Merge "Cleanup drop input for AE during legacy transition" into main
diff --git a/api/Android.bp b/api/Android.bp
index cd1997c..6a04f0d 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -368,8 +368,6 @@
         "--hide CallbackInterface",
         // Disable HiddenSuperclass, as Metalava handles this fine (it should be hidden by default)
         "--hide HiddenSuperclass",
-        "--hide-package android.audio.policy.configuration.V7_0",
-        "--hide-package com.android.server",
         "--manifest $(location :frameworks-base-core-AndroidManifest.xml)",
     ],
     filter_packages: packages_to_document,
diff --git a/core/api/current.txt b/core/api/current.txt
index 46f426b..889b627 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1058,7 +1058,7 @@
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
-    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") public static final int languageSettingsActivity;
+    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity;
     field public static final int languageTag = 16844040; // 0x1010508
     field public static final int largeHeap = 16843610; // 0x101035a
     field public static final int largeScreens = 16843398; // 0x1010286
@@ -6844,6 +6844,9 @@
     method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri);
   }
 
+  @FlaggedApi("android.app.api_rich_ongoing") public abstract static class Notification.RichOngoingStyle extends android.app.Notification.Style {
+  }
+
   public abstract static class Notification.Style {
     ctor @Deprecated public Notification.Style();
     method public android.app.Notification build();
@@ -11394,7 +11397,7 @@
     field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
-    field @FlaggedApi("android.service.chooser.enable_sharesheet_metadata_extra") public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
+    field public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
     field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
     field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
     field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
@@ -11603,6 +11606,7 @@
     method public static android.content.IntentSender readIntentSenderOrNullFromParcel(android.os.Parcel);
     method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler) throws android.content.IntentSender.SendIntentException;
     method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler, String) throws android.content.IntentSender.SendIntentException;
+    method @FlaggedApi("com.android.window.flags.bal_send_intent_with_options") public void sendIntent(@Nullable android.content.Context, int, @Nullable android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable java.util.concurrent.Executor, @Nullable android.content.IntentSender.OnFinished) throws android.content.IntentSender.SendIntentException;
     method public static void writeIntentSenderOrNullToParcel(android.content.IntentSender, android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.IntentSender> CREATOR;
@@ -56332,7 +56336,7 @@
   public final class InputMethodInfo implements android.os.Parcelable {
     ctor public InputMethodInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     ctor public InputMethodInfo(String, String, CharSequence, String);
-    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") @Nullable public android.content.Intent createImeLanguageSettingsActivityIntent();
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @Nullable public android.content.Intent createImeLanguageSettingsActivityIntent();
     method @Nullable public android.content.Intent createStylusHandwritingSettingsActivityIntent();
     method public int describeContents();
     method public void dump(android.util.Printer, String);
@@ -56353,7 +56357,7 @@
     method public boolean supportsStylusHandwriting();
     method public boolean suppressesSpellChecker();
     method public void writeToParcel(android.os.Parcel, int);
-    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") public static final String ACTION_IME_LANGUAGE_SETTINGS = "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
+    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final String ACTION_IME_LANGUAGE_SETTINGS = "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
     field public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
   }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db979a5..ef09dc4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -10979,6 +10979,18 @@
     }
 
     /**
+     * An object that can apply a rich ongoing notification style to a {@link Notification.Builder}
+     * object.
+     */
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+    public abstract static class RichOngoingStyle extends Notification.Style {
+        /**
+         * @hide
+         */
+        public RichOngoingStyle() {}
+    }
+
+    /**
      * Notification style for custom views that are decorated by the system
      *
      * <p>Instead of providing a notification that is completely custom, a developer can set this
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/core/java/android/audio/policy/configuration/V7_0/package-info.java
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to core/java/android/audio/policy/configuration/V7_0/package-info.java
index c92234c..8f7425f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/core/java/android/audio/policy/configuration/V7_0/package-info.java
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+/**
+ * Hide the android.audio.policy.configuration.V7_0 API as that is managed
+ * separately.
+ *
+ * @hide
+ */
+package android.audio.policy.configuration.V7_0;
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index d05d23c..55278f6 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -32,6 +32,13 @@
 }
 
 flag {
+     namespace: "virtual_devices"
+     name: "media_projection_keyguard_restrictions"
+     description: "Auto-stop MP when the device locks"
+     bug: "348335290"
+}
+
+flag {
     namespace: "virtual_devices"
     name: "virtual_display_insets"
     description: "APIs for specifying virtual display insets (via cutout)"
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 87fb843..e0553c8 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -2774,6 +2774,10 @@
                         + " provider from user:" + mContext.getUserId());
             }
             if (userId != UserHandle.USER_CURRENT
+                    // getUserIdFromAuthority can return USER_NULL when can't cast the userId to
+                    // an int, which can cause high volume of binder calls.
+                    && (!android.multiuser.Flags.fixGetUserPropertyCache()
+                        || userId != UserHandle.USER_NULL)
                     && userId != mContext.getUserId()
                     // Since userId specified in content uri, the provider userId would be
                     // determined from it.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 112507e..97404dc 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -20,7 +20,6 @@
 import static android.content.ContentProvider.maybeAddUserId;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
-import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -6252,7 +6251,6 @@
      * <p>e.g. When sharing a photo, metadata could inform the user that location data is included
      * in the photo they are sharing.</p>
      */
-    @FlaggedApi(FLAG_ENABLE_SHARESHEET_METADATA_EXTRA)
     public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
 
     /**
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 2e252c1..32d1964 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -16,6 +16,7 @@
 
 package android.content;
 
+import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.PendingIntentInfo;
@@ -32,6 +33,10 @@
 import android.os.UserHandle;
 import android.util.AndroidException;
 
+import com.android.window.flags.Flags;
+
+import java.util.concurrent.Executor;
+
 /**
  * A description of an Intent and target action to perform with it.
  * The returned object can be
@@ -114,15 +119,15 @@
             implements Runnable {
         private final IntentSender mIntentSender;
         private final OnFinished mWho;
-        private final Handler mHandler;
+        private final Executor mExecutor;
         private Intent mIntent;
         private int mResultCode;
         private String mResultData;
         private Bundle mResultExtras;
-        FinishedDispatcher(IntentSender pi, OnFinished who, Handler handler) {
+        FinishedDispatcher(IntentSender pi, OnFinished who, Executor executor) {
             mIntentSender = pi;
             mWho = who;
-            mHandler = handler;
+            mExecutor = executor;
         }
         public void performReceive(Intent intent, int resultCode, String data,
                 Bundle extras, boolean serialized, boolean sticky, int sendingUser) {
@@ -130,10 +135,10 @@
             mResultCode = resultCode;
             mResultData = data;
             mResultExtras = extras;
-            if (mHandler == null) {
+            if (mExecutor == null) {
                 run();
             } else {
-                mHandler.post(this);
+                mExecutor.execute(this);
             }
         }
         public void run() {
@@ -147,16 +152,16 @@
      * caller to specify information about the Intent to use and be notified
      * when the send has completed.
      *
-     * @param context The Context of the caller.  This may be null if
-     * <var>intent</var> is also null.
+     * @param context The Context of the caller.  This may be {@code null} if
+     * <var>intent</var> is also {@code null}.
      * @param code Result code to supply back to the IntentSender's target.
      * @param intent Additional Intent data.  See {@link Intent#fillIn
      * Intent.fillIn()} for information on how this is applied to the
-     * original Intent.  Use null to not modify the original Intent.
+     * original Intent.  Use {@code null} to not modify the original Intent.
      * @param onFinished The object to call back on when the send has
-     * completed, or null for no callback.
+     * completed, or {@code null} for no callback.
      * @param handler Handler identifying the thread on which the callback
-     * should happen.  If null, the callback will happen from the thread
+     * should happen.  If {@code null}, the callback will happen from the thread
      * pool of the process.
      *
      *
@@ -165,8 +170,8 @@
      */
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler) throws SendIntentException {
-        sendIntent(context, code, intent, onFinished, handler, null,
-                SEND_INTENT_DEFAULT_OPTIONS);
+        sendIntent(context, code, intent, null, SEND_INTENT_DEFAULT_OPTIONS,
+                handler == null ? null : handler::post, onFinished);
     }
 
     /**
@@ -174,22 +179,22 @@
      * caller to specify information about the Intent to use and be notified
      * when the send has completed.
      *
-     * @param context The Context of the caller.  This may be null if
-     * <var>intent</var> is also null.
+     * @param context The Context of the caller.  This may be {@code null} if
+     * <var>intent</var> is also {@code null}.
      * @param code Result code to supply back to the IntentSender's target.
      * @param intent Additional Intent data.  See {@link Intent#fillIn
      * Intent.fillIn()} for information on how this is applied to the
-     * original Intent.  Use null to not modify the original Intent.
+     * original Intent.  Use {@code null} to not modify the original Intent.
      * @param onFinished The object to call back on when the send has
-     * completed, or null for no callback.
+     * completed, or {@code null} for no callback.
      * @param handler Handler identifying the thread on which the callback
-     * should happen.  If null, the callback will happen from the thread
+     * should happen.  If {@code null}, the callback will happen from the thread
      * pool of the process.
      * @param requiredPermission Name of permission that a recipient of the PendingIntent
      * is required to hold.  This is only valid for broadcast intents, and
      * corresponds to the permission argument in
      * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
-     * If null, no permission is required.
+     * If {@code null}, no permission is required.
      *
      *
      * @throws SendIntentException Throws CanceledIntentException if the IntentSender
@@ -198,8 +203,8 @@
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler, String requiredPermission)
             throws SendIntentException {
-        sendIntent(context, code, intent, onFinished, handler, requiredPermission,
-                SEND_INTENT_DEFAULT_OPTIONS);
+        sendIntent(context, code, intent, requiredPermission, SEND_INTENT_DEFAULT_OPTIONS,
+                handler == null ? null : handler::post, onFinished);
     }
 
     /**
@@ -207,32 +212,32 @@
      * caller to specify information about the Intent to use and be notified
      * when the send has completed.
      *
-     * @param context The Context of the caller.  This may be null if
-     * <var>intent</var> is also null.
+     * @param context The Context of the caller.  This may be {@code null} if
+     * <var>intent</var> is also {@code null}.
      * @param code Result code to supply back to the IntentSender's target.
      * @param intent Additional Intent data.  See {@link Intent#fillIn
      * Intent.fillIn()} for information on how this is applied to the
-     * original Intent.  Use null to not modify the original Intent.
+     * original Intent.  Use {@code null} to not modify the original Intent.
      * @param onFinished The object to call back on when the send has
-     * completed, or null for no callback.
-     * @param handler Handler identifying the thread on which the callback
-     * should happen.  If null, the callback will happen from the thread
+     * completed, or {@code null} for no callback.
+     * @param executor Executor identifying the thread on which the callback
+     * should happen.  If {@code null}, the callback will happen from the thread
      * pool of the process.
      * @param requiredPermission Name of permission that a recipient of the PendingIntent
      * is required to hold.  This is only valid for broadcast intents, and
      * corresponds to the permission argument in
      * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
-     * If null, no permission is required.
+     * If {@code null}, no permission is required.
      * @param options Additional options the caller would like to provide to modify the sending
-     * behavior.  May be built from an {@link ActivityOptions} to apply to an activity start.
+     * behavior.  Typically built from using {@link ActivityOptions} to apply to an activity start.
      *
      * @throws SendIntentException Throws CanceledIntentException if the IntentSender
      * is no longer allowing more intents to be sent through it.
-     * @hide
      */
-    public void sendIntent(Context context, int code, Intent intent,
-            OnFinished onFinished, Handler handler, String requiredPermission,
-            @Nullable Bundle options)
+    @FlaggedApi(Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS)
+    public void sendIntent(@Nullable Context context, int code, @Nullable Intent intent,
+            @Nullable String requiredPermission, @Nullable Bundle options,
+            @Nullable Executor executor, @Nullable OnFinished onFinished)
             throws SendIntentException {
         try {
             String resolvedType = intent != null ?
@@ -243,7 +248,7 @@
             int res = ActivityManager.getService().sendIntentSender(app, mTarget, mWhitelistToken,
                     code, intent, resolvedType,
                     onFinished != null
-                            ? new FinishedDispatcher(this, onFinished, handler)
+                            ? new FinishedDispatcher(this, onFinished, executor)
                             : null,
                     requiredPermission, options);
             if (res < 0) {
@@ -268,7 +273,7 @@
      * sending the Intent.  The returned string is supplied by the system, so
      * that an application can not spoof its package.
      *
-     * @return The package name of the PendingIntent, or null if there is
+     * @return The package name of the PendingIntent, or {@code null} if there is
      * none associated with it.
      */
     public String getCreatorPackage() {
@@ -296,7 +301,7 @@
      * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
      * more explanation of user handles.
      *
-     * @return The user handle of the PendingIntent, or null if there is
+     * @return The user handle of the PendingIntent,  {@code null} if there is
      * none associated with it.
      */
     public UserHandle getCreatorUserHandle() {
@@ -355,11 +360,11 @@
     };
 
     /**
-     * Convenience function for writing either a IntentSender or null pointer to
+     * Convenience function for writing either a IntentSender or {@code null} pointer to
      * a Parcel.  You must use this with {@link #readIntentSenderOrNullFromParcel}
      * for later reading it.
      *
-     * @param sender The IntentSender to write, or null.
+     * @param sender The IntentSender to write, or {@code null}.
      * @param out Where to write the IntentSender.
      */
     public static void writeIntentSenderOrNullToParcel(IntentSender sender,
@@ -369,13 +374,13 @@
     }
 
     /**
-     * Convenience function for reading either a Messenger or null pointer from
+     * Convenience function for reading either a Messenger or {@code null} pointer from
      * a Parcel.  You must have previously written the Messenger with
      * {@link #writeIntentSenderOrNullToParcel}.
      *
      * @param in The Parcel containing the written Messenger.
      *
-     * @return Returns the Messenger read from the Parcel, or null if null had
+     * @return Returns the Messenger read from the Parcel, or @code null} if @code null} had
      * been written.
      */
     public static IntentSender readIntentSenderOrNullFromParcel(Parcel in) {
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c7d94c6..26ee4e8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -161,6 +161,16 @@
 }
 
 flag {
+    name: "cache_profile_parent"
+    namespace: "multiuser"
+    description: "Cache getProfileParent to avoid unnecessary binder calls"
+    bug: "350417399"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "cache_quiet_mode_state"
     namespace: "multiuser"
     description: "Optimise quiet mode state retrieval"
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index 3be911abe7..8c98abd 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -107,7 +107,8 @@
         }
 
         /**
-         * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive.
+         * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. By default, the scroll
+         * amount is 0, which results in no scroll.
          * <p>
          * Positive values indicate scrolling forward (e.g. down in a vertical list); negative
          * values, backward.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 16d9ef2..0cd2800 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -68,3 +68,10 @@
     description: "Controls if the mouse keys accessibility feature for physical keyboard is available to the user"
     bug: "341799888"
 }
+
+flag {
+    namespace: "input_native"
+    name: "touchpad_visualizer"
+    description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
+    bug: "286551975"
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
index 4bb66ed..93c5439 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
@@ -17,7 +17,6 @@
 package android.inputmethodservice.navigationbar;
 
 import android.annotation.ColorInt;
-import android.graphics.Color;
 
 final class NavigationBarConstants {
     private NavigationBarConstants() {
@@ -28,13 +27,13 @@
     // TODO(b/215443343): Handle this in the drawable then remove this constant.
     static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f;
 
-    // Copied from "white" at packages/SettingsLib/res/values/colors.xml
+    // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
     @ColorInt
-    static final int WHITE = Color.WHITE;
+    static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff;
 
-    // Copied from "black" at packages/SettingsLib/res/values/colors.xml
+    // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
     @ColorInt
-    static final int BLACK = Color.BLACK;
+    static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000;
 
     // Copied from "navigation_bar_deadzone_hold"
     static final int NAVIGATION_BAR_DEADZONE_HOLD = 333;
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index b522e9b..e28f345 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -16,8 +16,8 @@
 
 package android.inputmethodservice.navigationbar;
 
-import static android.inputmethodservice.navigationbar.NavigationBarConstants.BLACK;
-import static android.inputmethodservice.navigationbar.NavigationBarConstants.WHITE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
 import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
 import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -83,8 +83,8 @@
         super(context, attrs);
 
         mLightContext = context;
-        mLightIconColor = WHITE;
-        mDarkIconColor = BLACK;
+        mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+        mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
 
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f30a9f5..3aa42c6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3927,7 +3927,12 @@
             android.Manifest.permission.QUERY_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
-        return mUserPropertiesCache.query(userHandle.getIdentifier());
+        final int userId = userHandle.getIdentifier();
+        // Avoid calling into system server for invalid user ids.
+        if (android.multiuser.Flags.fixGetUserPropertyCache() && userId < 0) {
+            throw new IllegalArgumentException("Cannot access properties for user " + userId);
+        }
+        return mUserPropertiesCache.query(userId);
     }
 
     /**
@@ -5663,6 +5668,33 @@
         }
     }
 
+    private static final String CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY =
+        PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "quiet_mode_enabled");
+
+    private final PropertyInvalidatedCache<Integer, Boolean> mQuietModeEnabledCache =
+            new PropertyInvalidatedCache<Integer, Boolean>(
+                32, CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY) {
+                @Override
+                public Boolean recompute(Integer query) {
+                    try {
+                        return mService.isQuietModeEnabled(query);
+                    } catch (RemoteException re) {
+                        throw re.rethrowFromSystemServer();
+                    }
+                }
+                @Override
+                public boolean bypass(Integer query) {
+                    return query < 0;
+                }
+            };
+
+
+    /** @hide */
+    public static final void invalidateQuietModeEnabledCache() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY);
+    }
+
     /**
      * Returns whether the given profile is in quiet mode or not.
      *
@@ -5670,6 +5702,13 @@
      * @return true if the profile is in quiet mode, false otherwise.
      */
     public boolean isQuietModeEnabled(UserHandle userHandle) {
+        if (android.multiuser.Flags.cacheQuietModeState()){
+            final int userId = userHandle.getIdentifier();
+            if (userId < 0) {
+                return false;
+            }
+            return mQuietModeEnabledCache.query(userId);
+        }
         try {
             return mService.isQuietModeEnabled(userHandle.getIdentifier());
         } catch (RemoteException re) {
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 82c1613..1f1a351 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -2,14 +2,6 @@
 container: "system"
 
 flag {
-    name: "enable_sharesheet_metadata_extra"
-    is_exported: true
-    namespace: "intentresolver"
-    description: "This flag enables sharesheet metadata to be displayed to users."
-    bug: "318942069"
-}
-
-flag {
   name: "chooser_payload_toggling"
   is_exported: true
   namespace: "intentresolver"
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index fe98fab..075571c 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -162,6 +162,12 @@
         return mSource;
     }
 
+    /** Set the flags of this provider. */
+    public InsetsFrameProvider setFlags(@Flags int flags) {
+        mFlags = flags;
+        return this;
+    }
+
     public InsetsFrameProvider setFlags(@Flags int flags, @Flags int mask) {
         mFlags = (mFlags & ~mask) | (flags & mask);
         return this;
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 117b200..a806bd2 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -295,16 +295,7 @@
         }
 
         sCallStackDebuggingInitialized = true;
-        sCallStackDebuggingMatchCall =
-                SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
-                        .toLowerCase();
-        sCallStackDebuggingMatchName =
-                SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
-                        .toLowerCase();
-        sLogAllTxCallsOnApply = sCallStackDebuggingMatchCall.contains("apply");
-        // Only enable stack debugging if any of the match filters are set
-        sCallStackDebuggingEnabled = !sCallStackDebuggingMatchCall.isEmpty()
-                || !sCallStackDebuggingMatchName.isEmpty();
+        updateCallStackDebuggingParams();
         if (sCallStackDebuggingEnabled) {
             Log.d(TAG, "Enabling transaction call stack debugging:"
                     + " matchCall=" + sCallStackDebuggingMatchCall
@@ -325,6 +316,11 @@
     final void checkCallStackDebugging(@NonNull String call,
             @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
             @Nullable String details) {
+
+        if (sCallStackDebuggingInitialized && sCallStackDebuggingEnabled) {
+            updateCallStackDebuggingParams();
+        }
+
         if (!sCallStackDebuggingEnabled) {
             return;
         }
@@ -356,6 +352,22 @@
     }
 
     /**
+     * Updates the call stack debugging params from the system properties.
+     */
+    private static void updateCallStackDebuggingParams() {
+        sCallStackDebuggingMatchCall =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+                        .toLowerCase();
+        sCallStackDebuggingMatchName =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+                        .toLowerCase();
+        sLogAllTxCallsOnApply = sCallStackDebuggingMatchCall.contains("apply");
+        // Only enable stack debugging if any of the match filters are set
+        sCallStackDebuggingEnabled = !sCallStackDebuggingMatchCall.isEmpty()
+                || !sCallStackDebuggingMatchName.isEmpty();
+    }
+
+    /**
      * Tests whether the given surface control name/method call matches the filters set for the
      * call stack debugging.
      */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d224217..a6c6c18 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -130,6 +130,7 @@
 import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
 import static com.android.window.flags.Flags.activityWindowInfoFlag;
 import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
+import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption;
 import static com.android.window.flags.Flags.insetsControlChangedItem;
 import static com.android.window.flags.Flags.insetsControlSeq;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -3189,7 +3190,9 @@
         inOutParams.privateFlags &= ~PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
     }
 
-    private void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
+    /** Updates the {@link InsetsType}s to hide or show based on layout params. */
+    @VisibleForTesting
+    public void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
         final int sysUiVis = params.systemUiVisibility | params.subtreeSystemUiVisibility;
         final int flags = params.flags;
         final boolean matchParent = params.width == MATCH_PARENT && params.height == MATCH_PARENT;
@@ -3200,6 +3203,9 @@
                 || ((flags & FLAG_FULLSCREEN) != 0 && matchParent && nonAttachedAppWindow);
         final boolean navWasHiddenByFlags = (mTypesHiddenByFlags & Type.navigationBars()) != 0;
         final boolean navIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+        final boolean captionWasHiddenByFlags = (mTypesHiddenByFlags & Type.captionBar()) != 0;
+        final boolean captionIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_FULLSCREEN) != 0
+                || ((flags & FLAG_FULLSCREEN) != 0 && matchParent && nonAttachedAppWindow);
 
         @InsetsType int typesToHide = 0;
         @InsetsType int typesToShow = 0;
@@ -3213,6 +3219,13 @@
         } else if (!navIsHiddenByFlags && navWasHiddenByFlags) {
             typesToShow |= Type.navigationBars();
         }
+        if (captionIsHiddenByFlags && !captionWasHiddenByFlags
+                && enableCaptionCompatInsetForceConsumption()) {
+            typesToHide |= Type.captionBar();
+        } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags
+                && enableCaptionCompatInsetForceConsumption()) {
+            typesToShow |= Type.captionBar();
+        }
         if (typesToHide != 0) {
             getInsetsController().hide(typesToHide);
         }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 11ee286..098f655 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -92,7 +92,7 @@
      *
      * @see #createImeLanguageSettingsActivityIntent()
      */
-    @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP)
+    @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
     public static final String ACTION_IME_LANGUAGE_SETTINGS =
             "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
 
@@ -298,7 +298,7 @@
                     com.android.internal.R.styleable.InputMethod);
             settingsActivityComponent = sa.getString(
                     com.android.internal.R.styleable.InputMethod_settingsActivity);
-            if (Flags.imeSwitcherRevamp()) {
+            if (Flags.imeSwitcherRevampApi()) {
                 languageSettingsActivityComponent = sa.getString(
                         com.android.internal.R.styleable.InputMethod_languageSettingsActivity);
             }
@@ -888,7 +888,7 @@
      *
      * @attr ref R.styleable#InputMethod_languageSettingsActivity
      */
-    @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP)
+    @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
     @Nullable
     public Intent createImeLanguageSettingsActivityIntent() {
         if (TextUtils.isEmpty(mLanguageSettingsActivityName)) {
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 56e5bcf..e294ee2 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -82,6 +82,15 @@
 }
 
 flag {
+    name: "ime_switcher_revamp_api"
+    is_exported: true
+    namespace: "input_method"
+    description: "Feature flag for APIs for revamping the Input Method Switcher menu"
+    bug: "311791923"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "initiation_without_input_connection"
     namespace: "input_method"
     description: "Feature flag for initiating handwriting without InputConnection"
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 1034479..c53a0e1 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -36,6 +36,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
@@ -339,10 +340,10 @@
             if (sProviderInstance != null) return sProviderInstance;
 
             sTimestamps.mWebViewLoadStart = SystemClock.uptimeMillis();
-            final int uid = android.os.Process.myUid();
-            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
-                    || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
-                    || uid == android.os.Process.BLUETOOTH_UID) {
+            final int appId = UserHandle.getAppId(android.os.Process.myUid());
+            if (appId == android.os.Process.ROOT_UID || appId == android.os.Process.SYSTEM_UID
+                    || appId == android.os.Process.PHONE_UID || appId == android.os.Process.NFC_UID
+                    || appId == android.os.Process.BLUETOOTH_UID) {
                 throw new UnsupportedOperationException(
                         "For security reasons, WebView is not allowed in privileged processes");
             }
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index d28500c..12d4ab8 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,12 +16,15 @@
 
 package android.window;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.FloatProperty;
 import android.util.TimeUtils;
 import android.view.Choreographer;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.dynamicanimation.animation.DynamicAnimation;
 import com.android.internal.dynamicanimation.animation.FlingAnimation;
 import com.android.internal.dynamicanimation.animation.FloatValueHolder;
@@ -210,7 +213,8 @@
     }
 
     /** Returns true if the back animation is in progress. */
-    boolean isBackAnimationInProgress() {
+    @VisibleForTesting(visibility = PACKAGE)
+    public boolean isBackAnimationInProgress() {
         return mBackAnimationInProgress;
     }
 
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8ded608..1083f64 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -205,7 +205,7 @@
             FLAG_SYNC,
             FLAG_CONFIG_AT_END,
             FLAG_FIRST_CUSTOM
-    })
+    }, flag = true)
     public @interface ChangeFlags {}
 
     private final @TransitionType int mType;
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index cc22576..253337b 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -471,7 +471,7 @@
                 "pipTask = " + mPipTask + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
                 "displayChange = " + mDisplayChange + ", " +
-                "flags = " + mFlags + ", " +
+                "flags = " + Integer.toHexString(mFlags) + ", " +
                 "debugId = " + mDebugId +
         " }";
     }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index cd033ca..e9d77f8 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -42,6 +42,7 @@
 import android.os.Parcelable;
 import android.util.ArrayMap;
 import android.view.InsetsFrameProvider;
+import android.view.InsetsSource;
 import android.view.SurfaceControl;
 import android.view.WindowInsets.Type.InsetsType;
 
@@ -714,14 +715,16 @@
     @NonNull
     public WindowContainerTransaction addInsetsSource(
             @NonNull WindowContainerToken receiver,
-            IBinder owner, int index, @InsetsType int type, Rect frame, Rect[] boundingRects) {
+            IBinder owner, int index, @InsetsType int type, Rect frame, Rect[] boundingRects,
+            @InsetsSource.Flags int flags) {
         final HierarchyOp hierarchyOp =
                 new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
                         .setContainer(receiver.asBinder())
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
                                 .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
                                 .setArbitraryRectangle(frame)
-                                .setBoundingRects(boundingRects))
+                                .setBoundingRects(boundingRects)
+                                .setFlags(flags))
                         .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 9b87e23..7bbc3db 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -244,6 +244,7 @@
                 // We should call onBackCancelled() when an active callback is removed from
                 // dispatcher.
                 sendCancelledIfInProgress(callback);
+                mHandler.post(mProgressAnimator::reset);
                 setTopOnBackInvokedCallback(getTopCallback());
             }
         }
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 61fdcdd..f732929 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -40,8 +40,8 @@
 flag {
     name: "enable_windowing_edge_drag_resize"
     namespace: "lse_desktop_experience"
-    description: "Enables edge drag resizing for all input sources"
-    bug: "323383067"
+    description: "Enables edge drag resizing for stylus input (cursor is enabled by default)"
+    bug: "337782092"
 }
 
 flag {
@@ -162,3 +162,10 @@
     description: "Whether to enable multi-instance support in desktop windowing."
     bug: "336289597"
 }
+
+flag {
+    name: "enable_desktop_windowing_back_navigation"
+    namespace: "lse_desktop_experience"
+    description: "Whether to enable back navigation treatment in desktop windowing."
+    bug: "350421096"
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 92db37e..8fd525c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -206,3 +206,22 @@
   }
 }
 
+flag {
+  name: "enforce_shell_thread_model"
+  namespace: "windowing_frentend"
+  description: "Crash the shell process if someone calls in from the wrong thread"
+  bug: "351189446"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+flag {
+  name: "custom_animations_behind_translucent"
+  namespace: "windowing_frontend"
+  description: "A change can use its own layer parameters to animate behind a translucent activity"
+  bug: "327332488"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 7a8a47e..9d57041 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -342,8 +342,8 @@
                                                             .MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
                                             .toBundle();
                             try {
-                                mOnUnsuspend.sendIntent(this, 0, null, null, null, null,
-                                        activityOptions);
+                                mOnUnsuspend.sendIntent(this, 0, null, null, activityOptions,
+                                        null, null);
                             } catch (IntentSender.SendIntentException e) {
                                 Slog.e(TAG, "Error while starting intent " + mOnUnsuspend, e);
                             }
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1638699..2b096ea 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -160,8 +160,13 @@
      */
     public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
 
-    /** Track entering desktop mode interaction. */
-    public static final int CUJ_DESKTOP_MODE_ENTER_MODE = 107;
+    /**
+     * Track entering desktop mode interaction via app handle drag.
+     *
+     * <p>Tracking starts when the app handle is dragged and
+     * finishes when the window animation to desktop ends after app handle release.
+     */
+    public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG = 107;
 
     /** Track exiting desktop mode interaction. */
     public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108;
@@ -175,9 +180,17 @@
     /** Track launching a dialog from a status bar chip. */
     public static final int CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP = 111;
 
+    /**
+     * Track entering desktop mode interaction via app handle menu.
+     *
+     * <p>Tracking starts after windowing mode option in the app handle menu is clicked and
+     * finishes when the window animation to desktop ends.
+     */
+    public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU = 112;
+
 
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
 
     /** @hide */
     @IntDef({
@@ -276,7 +289,8 @@
             CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
             CUJ_FOLD_ANIM,
             CUJ_DESKTOP_MODE_RESIZE_WINDOW,
-            CUJ_DESKTOP_MODE_ENTER_MODE,
+            CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG,
+            CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU,
             CUJ_DESKTOP_MODE_EXIT_MODE,
             CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
             CUJ_DESKTOP_MODE_DRAG_WINDOW,
@@ -389,7 +403,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
@@ -602,8 +617,10 @@
                 return "FOLD_ANIM";
             case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
                 return "DESKTOP_MODE_RESIZE_WINDOW";
-            case CUJ_DESKTOP_MODE_ENTER_MODE:
-                return "DESKTOP_MODE_ENTER_MODE";
+            case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG:
+                return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG";
+            case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU:
+                return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU";
             case CUJ_DESKTOP_MODE_EXIT_MODE:
                 return "DESKTOP_MODE_EXIT_MODE";
             case CUJ_DESKTOP_MODE_MINIMIZE_WINDOW:
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 63ff598..87e22ed 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -109,6 +109,7 @@
 import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.BackgroundFallback;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
+import com.android.window.flags.Flags;
 
 import java.util.List;
 import java.util.function.Consumer;
@@ -1193,7 +1194,8 @@
         // If we should always consume system bars, only consume that if the app wanted to go to
         // fullscreen, as otherwise we can expect the app to handle it.
         boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
-                || (attrs.flags & FLAG_FULLSCREEN) != 0
+                || (attrs.flags & FLAG_FULLSCREEN) != 0;
+        final boolean hideStatusBar = fullscreen
                 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
         boolean consumingStatusBar =
                 ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
@@ -1203,9 +1205,20 @@
                         && mForceWindowDrawsBarBackgrounds
                         && mLastTopInset != 0)
                 || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
-                        && fullscreen);
+                        && hideStatusBar);
 
-        int consumedTop = consumingStatusBar ? mLastTopInset : 0;
+        final boolean hideCaptionBar = fullscreen
+                || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
+        final boolean consumingCaptionBar =
+                ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+                        && hideCaptionBar);
+
+        final int consumedTop;
+        if (Flags.enableCaptionCompatInsetForceConsumption()) {
+            consumedTop = (consumingStatusBar || consumingCaptionBar) ? mLastTopInset : 0;
+        } else {
+            consumedTop = consumingStatusBar ? mLastTopInset : 0;
+        }
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
         int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
         int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 6abba8b..50fb8d5 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -104,8 +104,8 @@
     private final Runnable mCacheUpdater;
 
     private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
-    private final Map<IProtoLogGroup, int[]> mLogLevelCounts = new ArrayMap<>();
-    private final Map<IProtoLogGroup, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
+    private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>();
+    private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
 
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
@@ -226,7 +226,7 @@
 
     @Override
     public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
-        final int[] groupLevelCount = mLogLevelCounts.get(group);
+        final int[] groupLevelCount = mLogLevelCounts.get(group.name());
         return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
                 || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
                 || group.isLogToLogcat();
@@ -279,7 +279,7 @@
         if (isProtoEnabled()) {
             long tsNanos = SystemClock.elapsedRealtimeNanos();
             final String stacktrace;
-            if (mCollectStackTraceGroupCounts.getOrDefault(group, 0) > 0) {
+            if (mCollectStackTraceGroupCounts.getOrDefault(group.name(), 0) > 0) {
                 stacktrace = collectStackTrace();
             } else {
                 stacktrace = null;
@@ -739,15 +739,8 @@
         final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
 
         for (String overriddenGroupTag : overriddenGroupTags) {
-            IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
-
-            if (group == null) {
-                throw new IllegalArgumentException("Trying to set config for \""
-                        + overriddenGroupTag + "\" that isn't registered");
-            }
-
-            mLogLevelCounts.putIfAbsent(group, new int[LogLevel.values().length]);
-            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+            mLogLevelCounts.putIfAbsent(overriddenGroupTag, new int[LogLevel.values().length]);
+            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(overriddenGroupTag);
 
             final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
             for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
@@ -755,13 +748,13 @@
             }
 
             if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
-                mCollectStackTraceGroupCounts.put(group,
-                        mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+                mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+                        mCollectStackTraceGroupCounts.getOrDefault(overriddenGroupTag, 0) + 1);
             }
 
             if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
-                mCollectStackTraceGroupCounts.put(group,
-                        mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+                mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+                        mCollectStackTraceGroupCounts.getOrDefault(overriddenGroupTag, 0) + 1);
             }
         }
 
@@ -781,24 +774,22 @@
         final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
 
         for (String overriddenGroupTag : overriddenGroupTags) {
-            IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
-
-            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(overriddenGroupTag);
 
             final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
-            for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
+            for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
                 logLevelsCountsForGroup[i]--;
             }
             if (Arrays.stream(logLevelsCountsForGroup).allMatch(it -> it == 0)) {
-                mLogLevelCounts.remove(group);
+                mLogLevelCounts.remove(overriddenGroupTag);
             }
 
             if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
-                mCollectStackTraceGroupCounts.put(group,
-                        mCollectStackTraceGroupCounts.get(group) - 1);
+                mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+                        mCollectStackTraceGroupCounts.get(overriddenGroupTag) - 1);
 
-                if (mCollectStackTraceGroupCounts.get(group) == 0) {
-                    mCollectStackTraceGroupCounts.remove(group);
+                if (mCollectStackTraceGroupCounts.get(overriddenGroupTag) == 0) {
+                    mCollectStackTraceGroupCounts.remove(overriddenGroupTag);
                 }
             }
         }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index eaff760..c07fd38 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2718,12 +2718,11 @@
 }
 
 static jint android_media_AudioSystem_getMaxSampleRate(JNIEnv *env, jobject thiz) {
-    // see frameworks/av/services/audiopolicy/common/include/policy.h
-    return 192000; // SAMPLE_RATE_HZ_MAX (for API)
+    return SAMPLE_RATE_HZ_MAX;
 }
 
 static jint android_media_AudioSystem_getMinSampleRate(JNIEnv *env, jobject thiz) {
-    return 4000; // SAMPLE_RATE_HZ_MIN  (for API)
+    return SAMPLE_RATE_HZ_MIN;
 }
 
 static std::vector<uid_t> convertJIntArrayToUidVector(JNIEnv *env, jintArray jArray) {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4892f59..0975eda 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4304,7 +4304,7 @@
         <attr name="settingsActivity" format="string" />
         <!-- Component name of an activity that allows the user to modify
              on-screen keyboards variants (e.g. different language or layout) for this service. -->
-        <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
+        <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
         <attr name="languageSettingsActivity" format="string"/>
         <!-- Set to true in all of the configurations for which this input
              method should be considered an option as the default. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b64334f..b74b41c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -114,7 +114,7 @@
     <public name="optional"/>
     <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
     <public name="adServiceTypes" />
-    <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
+    <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
     <public name="languageSettingsActivity"/>
     <!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
     <public name="dreamCategory"/>
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 36ab0d4..ce85a76 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -70,7 +70,7 @@
         assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
         assertThat(imi.supportsStylusHandwriting(), is(false));
         assertThat(imi.createStylusHandwritingSettingsActivityIntent(), equalTo(null));
-        if (Flags.imeSwitcherRevamp()) {
+        if (Flags.imeSwitcherRevampApi()) {
             assertThat(imi.createImeLanguageSettingsActivityIntent(), equalTo(null));
         }
     }
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index d4482f2..9ae96a0 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -348,12 +348,16 @@
 
         waitForIdle();
         verify(mCallback1).onBackStarted(any(BackEvent.class));
+        assertTrue(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
 
         mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
 
         waitForIdle();
         verify(mCallback1).onBackCancelled();
         verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
+        // Verify that ProgressAnimator is reset (and thus does not cause further callback event
+        // dispatching)
+        assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index bfee820..736d954 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -54,4 +54,11 @@
     public boolean hasCallback(Runnable r) {
         return mHandler.hasCallbacks(r);
     }
+
+    @Override
+    public void assertCurrentThread() {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            throw new IllegalStateException("must be called on " + mHandler);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index f729164..2c2961f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -96,4 +96,11 @@
      * See {@link android.os.Handler#hasCallbacks(Runnable)}.
      */
     boolean hasCallback(Runnable runnable);
+
+    /**
+     * May throw if the caller is not on the same thread as the executor.
+     */
+    default void assertCurrentThread() {
+       return;
+    }
 }
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 18157d6..dc3e2d0 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
@@ -159,18 +159,6 @@
             }
         }
 
-    private val transitionAreaHeight
-        get() =
-            context.resources.getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
-            )
-
-    private val transitionAreaWidth
-        get() =
-            context.resources.getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
-            )
-
     /** Task id of the task currently being dragged from fullscreen/split. */
     val draggingTaskId
         get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -776,12 +764,15 @@
             newTaskIdInFront ?: "null"
         )
 
-        if (Flags.enableDesktopWindowingWallpaperActivity()) {
-            // Add translucent wallpaper activity to show the wallpaper underneath
-            addWallpaperActivity(wct)
-        } else {
-            // Move home to front
-            moveHomeTask(wct, toTop = true)
+        // Currently, we only handle the desktop on the default display really.
+        if (displayId == DEFAULT_DISPLAY) {
+            if (Flags.enableDesktopWindowingWallpaperActivity()) {
+                // Add translucent wallpaper activity to show the wallpaper underneath
+                addWallpaperActivity(wct)
+            } else {
+                // Move home to front
+                moveHomeTask(wct, toTop = true)
+            }
         }
 
         val nonMinimizedTasksOrderedFrontToBack =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 3572d16..84f6af41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -68,7 +68,7 @@
 ## Tracing global SurfaceControl transaction updates
 
 While Winscope traces are very useful, it sometimes doesn't give you enough information about which
-part of the code is initiating the transaction updates.  In such cases, it can be helpful to get
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
 stack traces when specific surface transaction calls are made, which is possible by enabling the
 following system properties for example:
 ```shell
@@ -81,9 +81,11 @@
 # Disabling logging
 adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
 adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
-adb reboot
 ```
 
+A reboot is required to enable the logging. Once enabled, reboot is not needed to update the
+properties.
+
 It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
 noisy if unfiltered.
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index e277a8d..ea02de9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -816,6 +816,26 @@
         mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
         mSpringingToTouch = false;
         mDismissalPending = false;
+
+        // Check whether new bounds after fling imply we need to update stash state too.
+        stashEndActionIfNeeded();
+    }
+
+    private void stashEndActionIfNeeded() {
+        boolean isStashing = mPipBoundsState.getBounds().right > mPipBoundsState
+                .getDisplayBounds().width() || mPipBoundsState.getBounds().left < 0;
+        if (!isStashing) {
+            return;
+        }
+
+        if (mPipBoundsState.getBounds().left < 0
+                && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+            mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+        } else if (mPipBoundsState.getBounds().left >= 0
+                && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+            mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+        }
+        mMenuController.hideMenu();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 0c4ed26..53b80e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -106,9 +106,6 @@
 
     private float mStashVelocityThreshold;
 
-    // The reference inset bounds, used to determine the dismiss fraction
-    private final Rect mInsetBounds = new Rect();
-
     // Used to workaround an issue where the WM rotation happens before we are notified, allowing
     // us to send stale bounds
     private int mDeferResizeToNormalBoundsUntilRotation = -1;
@@ -206,17 +203,10 @@
                 mMotionHelper, mainExecutor);
         mTouchState = new PipTouchState(ViewConfiguration.get(context),
                 () -> {
-                    if (mPipBoundsState.isStashed()) {
-                        animateToUnStashedState();
-                        mPipUiEventLogger.log(
-                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
-                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
-                    } else {
-                        mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
-                                mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
-                                willResizeMenu(),
-                                shouldShowResizeHandle());
-                    }
+                    mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+                            mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+                            willResizeMenu(),
+                            shouldShowResizeHandle());
                 },
                 menuController::hideMenu,
                 mainExecutor);
@@ -438,7 +428,6 @@
         mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
         mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
         mDisplayRotation = displayRotation;
-        mInsetBounds.set(insetBounds);
         updateMovementBounds();
         mMovementBoundsExtraOffsets = extraOffset;
 
@@ -748,10 +737,13 @@
         final Rect pipBounds = mPipBoundsState.getBounds();
         final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
         final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
-        unStashedBounds.left = onLeftEdge ? mInsetBounds.left
-                : mInsetBounds.right - pipBounds.width();
-        unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
-                : mInsetBounds.right;
+
+        Rect insetBounds = new Rect();
+        mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+        unStashedBounds.left = onLeftEdge ? insetBounds.left
+                : insetBounds.right - pipBounds.width();
+        unStashedBounds.right = onLeftEdge ? insetBounds.left + pipBounds.width()
+                : insetBounds.right;
         mMotionHelper.animateToUnStashedBounds(unStashedBounds);
     }
 
@@ -899,8 +891,7 @@
                 // Reset the touch state on up before the fling settles
                 mTouchState.reset();
                 if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
-                    // mMotionHelper.stashToEdge(vel.x, vel.y,
-                    //      this::stashEndAction /* endAction */);
+                    mMotionHelper.stashToEdge(vel.x, vel.y, null /* endAction */);
                 } else {
                     if (mPipBoundsState.isStashed()) {
                         // Reset stashed state if previously stashed
@@ -1030,8 +1021,10 @@
      * resized.
      */
     private void updateMovementBounds() {
+        Rect insetBounds = new Rect();
+        mPipBoundsAlgorithm.getInsetBounds(insetBounds);
         mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
-                mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+                insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.onMovementBoundsChanged();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index a126cbe..9750d3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -535,7 +535,8 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         if (mCaptionInsets != null) {
             wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
-                    WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
+                    WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */,
+                    0 /* flags */);
         } else {
             wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
                     WindowInsets.Type.captionBar());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 73b32a2..f33a573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
 import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -985,7 +986,8 @@
         final int animType = options.getType();
         return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP
                 || animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN
-                || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS;
+                || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS
+                || animType == ANIM_FROM_STYLE;
     }
 
     private static void applyTransformation(long time, SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 21307a2..8d53beb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,12 +31,14 @@
 import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
+import static com.android.window.flags.Flags.enforceShellThreadModel;
 import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
 import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
@@ -806,14 +808,16 @@
         final int changeSize = info.getChanges().size();
         boolean taskChange = false;
         boolean transferStartingWindow = false;
-        int noAnimationBehindStartingWindow = 0;
+        int animBehindStartingWindow = 0;
         boolean allOccluded = changeSize > 0;
         for (int i = changeSize - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             taskChange |= change.getTaskInfo() != null;
             transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
-            if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
-                noAnimationBehindStartingWindow++;
+            if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)
+                    || change.hasAllFlags(
+                            FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+                animBehindStartingWindow++;
             }
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
@@ -831,11 +835,11 @@
         // There does not need animation when:
         // A. Transfer starting window. Apply transfer starting window directly if there is no other
         // task change. Since this is an activity->activity situation, we can detect it by selecting
-        // transitions with only 2 changes where
-        // 1. neither are tasks, and
+        // transitions with changes where
+        // 1. none are tasks, and
         // 2. one is a starting-window recipient, or all change is behind starting window.
-        if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
-                && changeSize == 2
+        if (!taskChange && (transferStartingWindow || animBehindStartingWindow == changeSize)
+                && changeSize >= 1
                 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
                 // changes are underneath another change.
                 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
@@ -921,9 +925,12 @@
         }
         // An existing animation is playing, so see if we can merge.
         final ActiveTransition playing = track.mActiveTransition;
+        final IBinder playingToken = playing.mToken;
+        final IBinder readyToken = ready.mToken;
+
         if (ready.mAborted) {
             // record as merged since it is no-op. Calls back into processReadyQueue
-            onMerged(playing, ready);
+            onMerged(playingToken, readyToken);
             return;
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
@@ -931,14 +938,31 @@
                 + " in case they can be merged", ready, playing);
         mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
-                playing.mToken, (wct) -> onMerged(playing, ready));
+                playing.mToken, (wct) -> onMerged(playingToken, readyToken));
     }
 
-    private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+    private void onMerged(@NonNull IBinder playingToken, @NonNull IBinder mergedToken) {
+        if (enforceShellThreadModel()) {
+            mMainExecutor.assertCurrentThread();
+        }
+
+        ActiveTransition playing = mKnownTransitions.get(playingToken);
+        if (playing == null) {
+            Log.e(TAG, "Merging into a non-existent transition: " + playingToken);
+            return;
+        }
+
+        ActiveTransition merged = mKnownTransitions.get(mergedToken);
+        if (merged == null) {
+            Log.e(TAG, "Merging a non-existent transition: " + mergedToken);
+            return;
+        }
+
         if (playing.getTrack() != merged.getTrack()) {
             throw new IllegalStateException("Can't merge across tracks: " + merged + " into "
                     + playing);
         }
+
         final Track track = mTracks.get(playing.getTrack());
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
                 merged, playing);
@@ -1068,13 +1092,17 @@
         info.releaseAnimSurfaces();
     }
 
-    private void onFinish(IBinder token,
-            @Nullable WindowContainerTransaction wct) {
+    private void onFinish(IBinder token, @Nullable WindowContainerTransaction wct) {
+        if (enforceShellThreadModel()) {
+            mMainExecutor.assertCurrentThread();
+        }
+
         final ActiveTransition active = mKnownTransitions.get(token);
         if (active == null) {
             Log.e(TAG, "Trying to finish a non-existent transition: " + token);
             return;
         }
+
         final Track track = mTracks.get(active.getTrack());
         if (track == null || track.mActiveTransition != active) {
             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5ffd883..5d662b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -852,18 +852,19 @@
      */
     void createHandleMenu(SplitScreenController splitScreenController) {
         loadAppInfoIfNeeded();
-        mHandleMenu = new HandleMenu.Builder(this)
-                .setAppIcon(mAppIconBitmap)
-                .setAppName(mAppName)
-                .setOnClickListener(mOnCaptionButtonClickListener)
-                .setOnTouchListener(mOnCaptionTouchListener)
-                .setLayoutId(mRelayoutParams.mLayoutResId)
-                .setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext))
-                .setCaptionHeight(mResult.mCaptionHeight)
-                .setDisplayController(mDisplayController)
-                .setSplitScreenController(splitScreenController)
-                .setBrowserLinkAvailable(browserLinkAvailable())
-                .build();
+        mHandleMenu = new HandleMenu(
+                this,
+                mRelayoutParams.mLayoutResId,
+                mOnCaptionButtonClickListener,
+                mOnCaptionTouchListener,
+                mAppIconBitmap,
+                mAppName,
+                mDisplayController,
+                splitScreenController,
+                DesktopModeStatus.canEnterDesktopMode(mContext),
+                browserLinkAvailable(),
+                mResult.mCaptionHeight
+        );
         mWindowDecorViewHolder.onHandleMenuOpened();
         mHandleMenu.show();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index a3616f6..32df8b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -468,8 +468,7 @@
                 case MotionEvent.ACTION_HOVER_ENTER:
                 case MotionEvent.ACTION_HOVER_MOVE: {
                     updateCursorType(e.getDisplayId(), e.getDeviceId(),
-                            e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(),
-                            e.getYCursorPosition());
+                            e.getPointerId(/*pointerIndex=*/0), e.getX(), e.getY());
                     result = true;
                     break;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index b5d1d4a..ba5f079 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,9 +33,6 @@
 import android.util.Size;
 import android.view.MotionEvent;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
 import com.android.wm.shell.R;
 
 import java.util.Objects;
@@ -44,11 +41,6 @@
  * Geometry for a drag resize region for a particular window.
  */
 final class DragResizeWindowGeometry {
-    // TODO(b/337264971) clean up when no longer needed
-    @VisibleForTesting static final boolean DEBUG = true;
-    // The additional width to apply to edge resize bounds just for logging when a touch is
-    // close.
-    @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
     private final int mTaskCornerRadius;
     private final Size mTaskSize;
     // The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -60,8 +52,6 @@
     private final @NonNull TaskCorners mFineTaskCorners;
     // The bounds for each edge drag region, which can resize the task in one direction.
     private final @NonNull TaskEdges mTaskEdges;
-    // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
-    private final @Nullable TaskEdges mDebugTaskEdges;
 
     DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
             int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -74,11 +64,6 @@
 
         // Save touch areas for each edge.
         mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
-        if (DEBUG) {
-            mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
-        } else {
-            mDebugTaskEdges = null;
-        }
     }
 
     /**
@@ -120,13 +105,7 @@
      */
     void union(@NonNull Region region) {
         // Apply the edge resize regions.
-        if (inDebugMode()) {
-            // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
-            // touch due to the size of the edge region.
-            mDebugTaskEdges.union(region);
-        } else {
-            mTaskEdges.union(region);
-        }
+        mTaskEdges.union(region);
 
         if (enableWindowingEdgeDragResize()) {
             // Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -219,10 +198,6 @@
 
     @DragPositioningCallback.CtrlType
     private int calculateEdgeResizeCtrlType(float x, float y) {
-        if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
-                    && !mTaskEdges.contains((int) x, (int) y))) {
-            return CTRL_TYPE_UNDEFINED;
-        }
         int ctrlType = CTRL_TYPE_UNDEFINED;
         // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
         // sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -313,9 +288,7 @@
                 && this.mResizeHandleThickness == other.mResizeHandleThickness
                 && this.mFineTaskCorners.equals(other.mFineTaskCorners)
                 && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
-                && (inDebugMode()
-                        ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
-                        : this.mTaskEdges.equals(other.mTaskEdges));
+                && this.mTaskEdges.equals(other.mTaskEdges);
     }
 
     @Override
@@ -326,11 +299,7 @@
                 mResizeHandleThickness,
                 mFineTaskCorners,
                 mLargeTaskCorners,
-                (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
-    }
-
-    private boolean inDebugMode() {
-        return DEBUG && mDebugTaskEdges != null;
+                mTaskEdges);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
deleted file mode 100644
index 7e44f32..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ /dev/null
@@ -1,539 +0,0 @@
-/*
- * 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.windowdecor;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
-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;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.window.SurfaceSyncGroup;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.window.flags.Flags;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
-
-/**
- * Handle menu opened when the appropriate button is clicked on.
- *
- * Displays up to 3 pills that show the following:
- * App Info: App name, app icon, and collapse button to close the menu.
- * Windowing Options(Proto 2 only): Buttons to change windowing modes.
- * Additional Options: Miscellaneous functions including screenshot and closing task.
- */
-class HandleMenu {
-    private static final String TAG = "HandleMenu";
-    private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
-    private final Context mContext;
-    private final DesktopModeWindowDecoration mParentDecor;
-    @VisibleForTesting
-    AdditionalViewContainer mHandleMenuViewContainer;
-    // Position of the handle menu used for laying out the handle view.
-    @VisibleForTesting
-    final PointF mHandleMenuPosition = new PointF();
-    // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
-    // may be in a different coordinate space than the input coordinates. Therefore, we still care
-    // about the menu's coordinates relative to the display as a whole, so we need to maintain
-    // those as well.
-    final Point mGlobalMenuPosition = new Point();
-    private final boolean mShouldShowWindowingPill;
-    private final boolean mShouldShowBrowserPill;
-    private final Bitmap mAppIconBitmap;
-    private final CharSequence mAppName;
-    private final View.OnClickListener mOnClickListener;
-    private final View.OnTouchListener mOnTouchListener;
-    private final RunningTaskInfo mTaskInfo;
-    private final DisplayController mDisplayController;
-    private final SplitScreenController mSplitScreenController;
-    private final int mLayoutResId;
-    private int mMarginMenuTop;
-    private int mMarginMenuStart;
-    private int mMenuHeight;
-    private int mMenuWidth;
-    private final int mCaptionHeight;
-    private HandleMenuAnimator mHandleMenuAnimator;
-
-
-    HandleMenu(DesktopModeWindowDecoration parentDecor, int layoutResId,
-            View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
-            Bitmap appIcon, CharSequence appName, DisplayController displayController,
-            SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
-            boolean shouldShowBrowserPill, int captionHeight) {
-        mParentDecor = parentDecor;
-        mContext = mParentDecor.mDecorWindowContext;
-        mTaskInfo = mParentDecor.mTaskInfo;
-        mDisplayController = displayController;
-        mSplitScreenController = splitScreenController;
-        mLayoutResId = layoutResId;
-        mOnClickListener = onClickListener;
-        mOnTouchListener = onTouchListener;
-        mAppIconBitmap = appIcon;
-        mAppName = appName;
-        mShouldShowWindowingPill = shouldShowWindowingPill;
-        mShouldShowBrowserPill = shouldShowBrowserPill;
-        mCaptionHeight = captionHeight;
-        loadHandleMenuDimensions();
-        updateHandleMenuPillPositions();
-    }
-
-    void show() {
-        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-
-        createHandleMenuViewContainer(t, ssg);
-        ssg.addTransaction(t);
-        ssg.markSyncReady();
-        setupHandleMenu();
-        animateHandleMenu();
-    }
-
-    private void createHandleMenuViewContainer(SurfaceControl.Transaction t,
-            SurfaceSyncGroup ssg) {
-        final int x = (int) mHandleMenuPosition.x;
-        final int y = (int) mHandleMenuPosition.y;
-        if (!mTaskInfo.isFreeform() && Flags.enableAdditionalWindowsAboveStatusBar()) {
-            mHandleMenuViewContainer = new AdditionalSystemViewContainer(mContext,
-                    R.layout.desktop_mode_window_decor_handle_menu, mTaskInfo.taskId,
-                    x, y, mMenuWidth, mMenuHeight);
-        } else {
-            mHandleMenuViewContainer = mParentDecor.addWindow(
-                    R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
-                    t, ssg, x, y, mMenuWidth, mMenuHeight);
-        }
-        final View handleMenuView = mHandleMenuViewContainer.getView();
-        mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
-    }
-
-    /**
-     * Animates the appearance of the handle menu and its three pills.
-     */
-    private void animateHandleMenu() {
-        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
-            mHandleMenuAnimator.animateCaptionHandleExpandToOpen();
-        } else {
-            mHandleMenuAnimator.animateOpen();
-        }
-    }
-
-    /**
-     * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
-     * pill.
-     */
-    private void setupHandleMenu() {
-        final View handleMenu = mHandleMenuViewContainer.getView();
-        handleMenu.setOnTouchListener(mOnTouchListener);
-        setupAppInfoPill(handleMenu);
-        if (mShouldShowWindowingPill) {
-            setupWindowingPill(handleMenu);
-        }
-        setupMoreActionsPill(handleMenu);
-        setupOpenInBrowserPill(handleMenu);
-    }
-
-    /**
-     * Set up interactive elements of handle menu's app info pill.
-     */
-    private void setupAppInfoPill(View handleMenu) {
-        final HandleMenuImageButton collapseBtn =
-                handleMenu.findViewById(R.id.collapse_menu_button);
-        final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
-        final TextView appName = handleMenu.findViewById(R.id.application_name);
-        collapseBtn.setOnClickListener(mOnClickListener);
-        collapseBtn.setTaskInfo(mTaskInfo);
-        appIcon.setImageBitmap(mAppIconBitmap);
-        appName.setText(mAppName);
-    }
-
-    /**
-     * Set up interactive elements and color of handle menu's windowing pill.
-     */
-    private void setupWindowingPill(View handleMenu) {
-        final ImageButton fullscreenBtn = handleMenu.findViewById(
-                R.id.fullscreen_button);
-        final ImageButton splitscreenBtn = handleMenu.findViewById(
-                R.id.split_screen_button);
-        final ImageButton floatingBtn = handleMenu.findViewById(R.id.floating_button);
-        // TODO: Remove once implemented.
-        floatingBtn.setVisibility(View.GONE);
-
-        final ImageButton desktopBtn = handleMenu.findViewById(R.id.desktop_button);
-        fullscreenBtn.setOnClickListener(mOnClickListener);
-        splitscreenBtn.setOnClickListener(mOnClickListener);
-        floatingBtn.setOnClickListener(mOnClickListener);
-        desktopBtn.setOnClickListener(mOnClickListener);
-        // The button corresponding to the windowing mode that the task is currently in uses a
-        // different color than the others.
-        final ColorStateList[] iconColors = getWindowingIconColor();
-        final ColorStateList inActiveColorStateList = iconColors[0];
-        final ColorStateList activeColorStateList = iconColors[1];
-        final int windowingMode = mTaskInfo.getWindowingMode();
-        fullscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_FULLSCREEN
-                ? activeColorStateList : inActiveColorStateList);
-        splitscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_MULTI_WINDOW
-                ? activeColorStateList : inActiveColorStateList);
-        floatingBtn.setImageTintList(windowingMode == WINDOWING_MODE_PINNED
-                ? activeColorStateList : inActiveColorStateList);
-        desktopBtn.setImageTintList(windowingMode == WINDOWING_MODE_FREEFORM
-                ? activeColorStateList : inActiveColorStateList);
-    }
-
-    /**
-     * Set up interactive elements & height of handle menu's more actions pill
-     */
-    private void setupMoreActionsPill(View handleMenu) {
-        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
-            handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
-        }
-    }
-
-    private void setupOpenInBrowserPill(View handleMenu) {
-        if (!mShouldShowBrowserPill) {
-            handleMenu.findViewById(R.id.open_in_browser_pill).setVisibility(View.GONE);
-            return;
-        }
-        final Button browserButton = handleMenu.findViewById(R.id.open_in_browser_button);
-        browserButton.setOnClickListener(mOnClickListener);
-    }
-
-    /**
-     * Returns array of windowing icon color based on current UI theme. First element of the
-     * array is for inactive icons and the second is for active icons.
-     */
-    private ColorStateList[] getWindowingIconColor() {
-        final int mode = mContext.getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK;
-        final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
-        final TypedArray typedArray = mContext.obtainStyledAttributes(new int[]{
-                com.android.internal.R.attr.materialColorOnSurface,
-                com.android.internal.R.attr.materialColorPrimary});
-        final int inActiveColor = typedArray.getColor(0, isNightMode ? Color.WHITE : Color.BLACK);
-        final int activeColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
-        typedArray.recycle();
-        return new ColorStateList[]{ColorStateList.valueOf(inActiveColor),
-                ColorStateList.valueOf(activeColor)};
-    }
-
-    /**
-     * Updates handle menu's position variables to reflect its next position.
-     */
-    private void updateHandleMenuPillPositions() {
-        int menuX;
-        final int menuY;
-        final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
-        updateGlobalMenuPosition(taskBounds);
-        if (mLayoutResId == R.layout.desktop_mode_app_header) {
-            // Align the handle menu to the left side of the caption.
-            menuX = mMarginMenuStart;
-            menuY = mMarginMenuTop;
-        } else {
-            if (Flags.enableAdditionalWindowsAboveStatusBar()) {
-                // In a focused decor, we use global coordinates for handle menu. Therefore we
-                // need to account for other factors like split stage and menu/handle width to
-                // center the menu.
-                final DisplayLayout layout = mDisplayController
-                        .getDisplayLayout(mTaskInfo.displayId);
-                menuX = mGlobalMenuPosition.x + ((mMenuWidth - layout.width()) / 2);
-                menuY = mGlobalMenuPosition.y + ((mMenuHeight - layout.height()) / 2);
-            } else {
-                menuX = (taskBounds.width() / 2) - (mMenuWidth / 2);
-                menuY = mMarginMenuTop;
-            }
-        }
-        // Handle Menu position setup.
-        mHandleMenuPosition.set(menuX, menuY);
-    }
-
-    private void updateGlobalMenuPosition(Rect taskBounds) {
-        if (mTaskInfo.isFreeform()) {
-            mGlobalMenuPosition.set(taskBounds.left + mMarginMenuStart,
-                    taskBounds.top + mMarginMenuTop);
-        } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-            mGlobalMenuPosition.set(
-                    (taskBounds.width() / 2) - (mMenuWidth / 2) + mMarginMenuStart,
-                    mMarginMenuTop
-            );
-        } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
-            final int splitPosition = mSplitScreenController.getSplitPosition(mTaskInfo.taskId);
-            final Rect leftOrTopStageBounds = new Rect();
-            final Rect rightOrBottomStageBounds = new Rect();
-            mSplitScreenController.getStageBounds(leftOrTopStageBounds,
-                    rightOrBottomStageBounds);
-            // TODO(b/343561161): This needs to be calculated differently if the task is in
-            //  top/bottom split.
-            if (splitPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
-                mGlobalMenuPosition.set(leftOrTopStageBounds.width()
-                                + (rightOrBottomStageBounds.width() / 2)
-                                - (mMenuWidth / 2) + mMarginMenuStart,
-                        mMarginMenuTop);
-            } else if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
-                mGlobalMenuPosition.set((leftOrTopStageBounds.width() / 2)
-                                - (mMenuWidth / 2) + mMarginMenuStart,
-                        mMarginMenuTop);
-            }
-        }
-    }
-
-    /**
-     * Update pill layout, in case task changes have caused positioning to change.
-     */
-    void relayout(SurfaceControl.Transaction t) {
-        if (mHandleMenuViewContainer != null) {
-            updateHandleMenuPillPositions();
-            mHandleMenuViewContainer.setPosition(t, mHandleMenuPosition.x, mHandleMenuPosition.y);
-        }
-    }
-
-    /**
-     * Check a passed MotionEvent if a click or hover has occurred on any button on this caption
-     * Note this should only be called when a regular onClick/onHover is not possible
-     * (i.e. the button was clicked through status bar layer)
-     *
-     * @param ev the MotionEvent to compare against.
-     */
-    void checkMotionEvent(MotionEvent ev) {
-        // If the menu view is above status bar, we can let the views handle input directly.
-        if (isViewAboveStatusBar()) return;
-        final View handleMenu = mHandleMenuViewContainer.getView();
-        final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
-        final PointF inputPoint = translateInputToLocalSpace(ev);
-        final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y);
-        final int action = ev.getActionMasked();
-        collapse.setHovered(inputInCollapseButton && action != ACTION_UP);
-        collapse.setPressed(inputInCollapseButton && action == ACTION_DOWN);
-        if (action == ACTION_UP && inputInCollapseButton) {
-            collapse.performClick();
-        }
-    }
-
-    private boolean isViewAboveStatusBar() {
-        return Flags.enableAdditionalWindowsAboveStatusBar()
-                && !mTaskInfo.isFreeform();
-    }
-
-    // Translate the input point from display coordinates to the same space as the handle menu.
-    private PointF translateInputToLocalSpace(MotionEvent ev) {
-        return new PointF(ev.getX() - mHandleMenuPosition.x,
-                ev.getY() - mHandleMenuPosition.y);
-    }
-
-    /**
-     * A valid menu input is one of the following:
-     * An input that happens in the menu views.
-     * Any input before the views have been laid out.
-     *
-     * @param inputPoint the input to compare against.
-     */
-    boolean isValidMenuInput(PointF inputPoint) {
-        if (!viewsLaidOut()) return true;
-        if (!isViewAboveStatusBar()) {
-            return pointInView(
-                    mHandleMenuViewContainer.getView(),
-                    inputPoint.x - mHandleMenuPosition.x,
-                    inputPoint.y - mHandleMenuPosition.y);
-        } else {
-            // Handle menu exists in a different coordinate space when added to WindowManager.
-            // Therefore we must compare the provided input coordinates to global menu coordinates.
-            // This includes factoring for split stage as input coordinates are relative to split
-            // stage position, not relative to the display as a whole.
-            PointF inputRelativeToMenu = new PointF(
-                    inputPoint.x - mGlobalMenuPosition.x,
-                    inputPoint.y - mGlobalMenuPosition.y
-            );
-            if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
-                    == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
-                // TODO(b/343561161): This also needs to be calculated differently if
-                //  the task is in top/bottom split.
-                Rect leftStageBounds = new Rect();
-                mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
-                inputRelativeToMenu.x += leftStageBounds.width();
-            }
-            return pointInView(
-                    mHandleMenuViewContainer.getView(),
-                    inputRelativeToMenu.x,
-                    inputRelativeToMenu.y);
-        }
-    }
-
-    private boolean pointInView(View v, float x, float y) {
-        return v != null && v.getLeft() <= x && v.getRight() >= x
-                && v.getTop() <= y && v.getBottom() >= y;
-    }
-
-    /**
-     * Check if the views for handle menu can be seen.
-     */
-    private boolean viewsLaidOut() {
-        return mHandleMenuViewContainer.getView().isLaidOut();
-    }
-
-    private void loadHandleMenuDimensions() {
-        final Resources resources = mContext.getResources();
-        mMenuWidth = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_width);
-        mMenuHeight = getHandleMenuHeight(resources);
-        mMarginMenuTop = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_margin_top);
-        mMarginMenuStart = loadDimensionPixelSize(resources,
-                R.dimen.desktop_mode_handle_menu_margin_start);
-    }
-
-    /**
-     * Determines handle menu height based on if windowing pill should be shown.
-     */
-    private int getHandleMenuHeight(Resources resources) {
-        int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
-        if (!mShouldShowWindowingPill) {
-            menuHeight -= loadDimensionPixelSize(resources,
-                    R.dimen.desktop_mode_handle_menu_windowing_pill_height);
-        }
-        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
-            menuHeight -= loadDimensionPixelSize(resources,
-                    R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
-        }
-        if (!mShouldShowBrowserPill) {
-            menuHeight -= loadDimensionPixelSize(resources,
-                    R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height);
-        }
-        return menuHeight;
-    }
-
-    private int loadDimensionPixelSize(Resources resources, int resourceId) {
-        if (resourceId == Resources.ID_NULL) {
-            return 0;
-        }
-        return resources.getDimensionPixelSize(resourceId);
-    }
-
-    void close() {
-        final Runnable after = () -> {
-            mHandleMenuViewContainer.releaseView();
-            mHandleMenuViewContainer = null;
-        };
-        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
-            mHandleMenuAnimator.animateCollapseIntoHandleClose(after);
-        } else {
-            mHandleMenuAnimator.animateClose(after);
-        }
-    }
-
-    static final class Builder {
-        private final DesktopModeWindowDecoration mParent;
-        private CharSequence mName;
-        private Bitmap mAppIcon;
-        private View.OnClickListener mOnClickListener;
-        private View.OnTouchListener mOnTouchListener;
-        private int mLayoutId;
-        private boolean mShowWindowingPill;
-        private int mCaptionHeight;
-        private DisplayController mDisplayController;
-        private SplitScreenController mSplitScreenController;
-        private boolean mShowBrowserPill;
-
-        Builder(@NonNull DesktopModeWindowDecoration parent) {
-            mParent = parent;
-        }
-
-        Builder setAppName(@Nullable CharSequence name) {
-            mName = name;
-            return this;
-        }
-
-        Builder setAppIcon(@Nullable Bitmap appIcon) {
-            mAppIcon = appIcon;
-            return this;
-        }
-
-        Builder setOnClickListener(@Nullable View.OnClickListener onClickListener) {
-            mOnClickListener = onClickListener;
-            return this;
-        }
-
-        Builder setOnTouchListener(@Nullable View.OnTouchListener onTouchListener) {
-            mOnTouchListener = onTouchListener;
-            return this;
-        }
-
-        Builder setLayoutId(int layoutId) {
-            mLayoutId = layoutId;
-            return this;
-        }
-
-        Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
-            mShowWindowingPill = windowingButtonsVisible;
-            return this;
-        }
-
-        Builder setCaptionHeight(int captionHeight) {
-            mCaptionHeight = captionHeight;
-            return this;
-        }
-
-        Builder setDisplayController(DisplayController displayController) {
-            mDisplayController = displayController;
-            return this;
-        }
-
-        Builder setSplitScreenController(SplitScreenController splitScreenController) {
-            mSplitScreenController = splitScreenController;
-            return this;
-        }
-
-        Builder setBrowserLinkAvailable(Boolean showBrowserPill) {
-            mShowBrowserPill = showBrowserPill;
-            return this;
-        }
-
-        HandleMenu build() {
-            return new HandleMenu(mParent, mLayoutId, mOnClickListener,
-                    mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
-                    mShowWindowingPill, mShowBrowserPill, mCaptionHeight);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
new file mode 100644
index 0000000..39595cf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -0,0 +1,491 @@
+/*
+ * 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.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+import android.view.View
+import android.widget.Button
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import android.window.SurfaceSyncGroup
+import androidx.annotation.VisibleForTesting
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.split.SplitScreenConstants
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
+import com.android.wm.shell.windowdecor.extension.isFullscreen
+
+/**
+ * Handle menu opened when the appropriate button is clicked on.
+ *
+ * Displays up to 3 pills that show the following:
+ * App Info: App name, app icon, and collapse button to close the menu.
+ * Windowing Options(Proto 2 only): Buttons to change windowing modes.
+ * Additional Options: Miscellaneous functions including screenshot and closing task.
+ */
+class HandleMenu(
+    private val parentDecor: DesktopModeWindowDecoration,
+    private val layoutResId: Int,
+    private val onClickListener: View.OnClickListener?,
+    private val onTouchListener: View.OnTouchListener?,
+    private val appIconBitmap: Bitmap?,
+    private val appName: CharSequence?,
+    private val displayController: DisplayController,
+    private val splitScreenController: SplitScreenController,
+    private val shouldShowWindowingPill: Boolean,
+    private val shouldShowBrowserPill: Boolean,
+    private val captionHeight: Int
+) {
+    private val context: Context = parentDecor.mDecorWindowContext
+    private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
+
+    private val isViewAboveStatusBar: Boolean
+        get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
+
+    private var marginMenuTop = 0
+    private var marginMenuStart = 0
+    private var menuHeight = 0
+    private var menuWidth = 0
+    private var handleMenuAnimator: HandleMenuAnimator? = null
+
+    @VisibleForTesting
+    var handleMenuViewContainer: AdditionalViewContainer? = null
+
+    // Position of the handle menu used for laying out the handle view.
+    @VisibleForTesting
+    val handleMenuPosition: PointF = PointF()
+
+    // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
+    // may be in a different coordinate space than the input coordinates. Therefore, we still care
+    // about the menu's coordinates relative to the display as a whole, so we need to maintain
+    // those as well.
+    private val globalMenuPosition: Point = Point()
+
+    /**
+     * An a array of windowing icon color based on current UI theme. First element of the
+     * array is for inactive icons and the second is for active icons.
+     */
+    private val windowingIconColor: Array<ColorStateList>
+        get() {
+            val mode = (context.resources.configuration.uiMode
+                    and Configuration.UI_MODE_NIGHT_MASK)
+            val isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES)
+            val typedArray = context.obtainStyledAttributes(
+                intArrayOf(
+                    com.android.internal.R.attr.materialColorOnSurface,
+                    com.android.internal.R.attr.materialColorPrimary
+                )
+            )
+            val inActiveColor =
+                typedArray.getColor(0, if (isNightMode) Color.WHITE else Color.BLACK)
+            val activeColor = typedArray.getColor(1, if (isNightMode) Color.WHITE else Color.BLACK)
+            typedArray.recycle()
+            return arrayOf(
+                ColorStateList.valueOf(inActiveColor),
+                ColorStateList.valueOf(activeColor)
+            )
+        }
+
+    init {
+        loadHandleMenuDimensions()
+        updateHandleMenuPillPositions()
+    }
+
+    fun show() {
+        val ssg = SurfaceSyncGroup(TAG)
+        val t = SurfaceControl.Transaction()
+
+        createHandleMenuViewContainer(t, ssg)
+        ssg.addTransaction(t)
+        ssg.markSyncReady()
+        setupHandleMenu()
+        animateHandleMenu()
+    }
+
+    private fun createHandleMenuViewContainer(
+        t: SurfaceControl.Transaction,
+        ssg: SurfaceSyncGroup
+    ) {
+        val x = handleMenuPosition.x.toInt()
+        val y = handleMenuPosition.y.toInt()
+        handleMenuViewContainer =
+            if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
+                AdditionalSystemViewContainer(
+                    context = context,
+                    layoutId = R.layout.desktop_mode_window_decor_handle_menu,
+                    taskId = taskInfo.taskId,
+                    x = x,
+                    y = y,
+                    width = menuWidth,
+                    height = menuHeight
+                )
+            } else {
+                parentDecor.addWindow(
+                    R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
+                    t, ssg, x, y, menuWidth, menuHeight
+                )
+            }
+        handleMenuViewContainer?.view?.let { view ->
+            handleMenuAnimator =
+                HandleMenuAnimator(view, menuWidth, captionHeight.toFloat())
+        }
+    }
+
+    /**
+     * Animates the appearance of the handle menu and its three pills.
+     */
+    private fun animateHandleMenu() {
+        when (taskInfo.windowingMode) {
+            WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+            WINDOWING_MODE_MULTI_WINDOW -> {
+                handleMenuAnimator?.animateCaptionHandleExpandToOpen()
+            }
+            else -> {
+                handleMenuAnimator?.animateOpen()
+            }
+        }
+    }
+
+    /**
+     * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
+     * pill.
+     */
+    private fun setupHandleMenu() {
+        val handleMenu = handleMenuViewContainer?.view ?: return
+        handleMenu.setOnTouchListener(onTouchListener)
+        setupAppInfoPill(handleMenu)
+        if (shouldShowWindowingPill) {
+            setupWindowingPill(handleMenu)
+        }
+        setupMoreActionsPill(handleMenu)
+        setupOpenInBrowserPill(handleMenu)
+    }
+
+    /**
+     * Set up interactive elements of handle menu's app info pill.
+     */
+    private fun setupAppInfoPill(handleMenu: View) {
+        val collapseBtn = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+        val appIcon = handleMenu.findViewById<ImageView>(R.id.application_icon)
+        val appName = handleMenu.findViewById<TextView>(R.id.application_name)
+        collapseBtn.setOnClickListener(onClickListener)
+        collapseBtn.taskInfo = taskInfo
+        appIcon.setImageBitmap(appIconBitmap)
+        appName.text = this.appName
+    }
+
+    /**
+     * Set up interactive elements and color of handle menu's windowing pill.
+     */
+    private fun setupWindowingPill(handleMenu: View) {
+        val fullscreenBtn = handleMenu.findViewById<ImageButton>(R.id.fullscreen_button)
+        val splitscreenBtn = handleMenu.findViewById<ImageButton>(R.id.split_screen_button)
+        val floatingBtn = handleMenu.findViewById<ImageButton>(R.id.floating_button)
+        // TODO: Remove once implemented.
+        floatingBtn.visibility = View.GONE
+
+        val desktopBtn = handleMenu.findViewById<ImageButton>(R.id.desktop_button)
+        fullscreenBtn.setOnClickListener(onClickListener)
+        splitscreenBtn.setOnClickListener(onClickListener)
+        floatingBtn.setOnClickListener(onClickListener)
+        desktopBtn.setOnClickListener(onClickListener)
+        // The button corresponding to the windowing mode that the task is currently in uses a
+        // different color than the others.
+        val iconColors = windowingIconColor
+        val inActiveColorStateList = iconColors[0]
+        val activeColorStateList = iconColors[1]
+        fullscreenBtn.imageTintList = if (taskInfo.isFullscreen) {
+            activeColorStateList
+        } else {
+            inActiveColorStateList
+        }
+        splitscreenBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+            activeColorStateList
+        } else {
+            inActiveColorStateList
+        }
+        floatingBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_PINNED) {
+            activeColorStateList
+        } else {
+            inActiveColorStateList
+        }
+        desktopBtn.imageTintList = if (taskInfo.isFreeform) {
+            activeColorStateList
+        } else {
+            inActiveColorStateList
+        }
+    }
+
+    /**
+     * Set up interactive elements & height of handle menu's more actions pill
+     */
+    private fun setupMoreActionsPill(handleMenu: View) {
+        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+            handleMenu.findViewById<View>(R.id.more_actions_pill).visibility = View.GONE
+        }
+    }
+
+    private fun setupOpenInBrowserPill(handleMenu: View) {
+        if (!shouldShowBrowserPill) {
+            handleMenu.findViewById<View>(R.id.open_in_browser_pill).visibility = View.GONE
+            return
+        }
+        val browserButton = handleMenu.findViewById<Button>(R.id.open_in_browser_button)
+        browserButton.setOnClickListener(onClickListener)
+    }
+
+    /**
+     * Updates handle menu's position variables to reflect its next position.
+     */
+    private fun updateHandleMenuPillPositions() {
+        val menuX: Int
+        val menuY: Int
+        val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds
+        updateGlobalMenuPosition(taskBounds)
+        if (layoutResId == R.layout.desktop_mode_app_header) {
+            // Align the handle menu to the left side of the caption.
+            menuX = marginMenuStart
+            menuY = marginMenuTop
+        } else {
+            if (Flags.enableAdditionalWindowsAboveStatusBar()) {
+                // In a focused decor, we use global coordinates for handle menu. Therefore we
+                // need to account for other factors like split stage and menu/handle width to
+                // center the menu.
+                menuX = globalMenuPosition.x
+                menuY = globalMenuPosition.y
+            } else {
+                menuX = (taskBounds.width() / 2) - (menuWidth / 2)
+                menuY = marginMenuTop
+            }
+        }
+        // Handle Menu position setup.
+        handleMenuPosition.set(menuX.toFloat(), menuY.toFloat())
+    }
+
+    private fun updateGlobalMenuPosition(taskBounds: Rect) {
+        when (taskInfo.windowingMode) {
+            WINDOWING_MODE_FREEFORM -> {
+                globalMenuPosition.set(
+                    /* x = */ taskBounds.left + marginMenuStart,
+                    /* y = */ taskBounds.top + marginMenuTop
+                )
+            }
+            WINDOWING_MODE_FULLSCREEN -> {
+                globalMenuPosition.set(
+                    /* x = */ taskBounds.width() / 2 - (menuWidth / 2),
+                    /* y = */ marginMenuTop
+                )
+            }
+            WINDOWING_MODE_MULTI_WINDOW -> {
+                val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId)
+                val leftOrTopStageBounds = Rect()
+                val rightOrBottomStageBounds = Rect()
+                splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
+                // TODO(b/343561161): This needs to be calculated differently if the task is in
+                //  top/bottom split.
+                when (splitPosition) {
+                    SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
+                        globalMenuPosition.set(
+                            /* x = */ leftOrTopStageBounds.width()
+                                    + (rightOrBottomStageBounds.width() / 2)
+                                    - (menuWidth / 2),
+                            /* y = */ marginMenuTop
+                        )
+                    }
+                    SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
+                        globalMenuPosition.set(
+                            /* x = */ (leftOrTopStageBounds.width() / 2)
+                                    - (menuWidth / 2),
+                            /* y = */ marginMenuTop
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update pill layout, in case task changes have caused positioning to change.
+     */
+    fun relayout(t: SurfaceControl.Transaction) {
+        handleMenuViewContainer?.let { container ->
+            updateHandleMenuPillPositions()
+            container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y)
+        }
+    }
+
+    /**
+     * Check a passed MotionEvent if a click or hover has occurred on any button on this caption
+     * Note this should only be called when a regular onClick/onHover is not possible
+     * (i.e. the button was clicked through status bar layer)
+     *
+     * @param ev the MotionEvent to compare against.
+     */
+    fun checkMotionEvent(ev: MotionEvent) {
+        // If the menu view is above status bar, we can let the views handle input directly.
+        if (isViewAboveStatusBar) return
+        val handleMenu = handleMenuViewContainer?.view ?: return
+        val collapse = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+        val inputPoint = translateInputToLocalSpace(ev)
+        val inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y)
+        val action = ev.actionMasked
+        collapse.isHovered = inputInCollapseButton && action != MotionEvent.ACTION_UP
+        collapse.isPressed = inputInCollapseButton && action == MotionEvent.ACTION_DOWN
+        if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
+            collapse.performClick()
+        }
+    }
+
+    // Translate the input point from display coordinates to the same space as the handle menu.
+    private fun translateInputToLocalSpace(ev: MotionEvent): PointF {
+        return PointF(
+            ev.x - handleMenuPosition.x,
+            ev.y - handleMenuPosition.y
+        )
+    }
+
+    /**
+     * A valid menu input is one of the following:
+     * An input that happens in the menu views.
+     * Any input before the views have been laid out.
+     *
+     * @param inputPoint the input to compare against.
+     */
+    fun isValidMenuInput(inputPoint: PointF): Boolean {
+        if (!viewsLaidOut()) return true
+        if (!isViewAboveStatusBar) {
+            return pointInView(
+                handleMenuViewContainer?.view,
+                inputPoint.x - handleMenuPosition.x,
+                inputPoint.y - handleMenuPosition.y
+            )
+        } else {
+            // Handle menu exists in a different coordinate space when added to WindowManager.
+            // Therefore we must compare the provided input coordinates to global menu coordinates.
+            // This includes factoring for split stage as input coordinates are relative to split
+            // stage position, not relative to the display as a whole.
+            val inputRelativeToMenu = PointF(
+                inputPoint.x - globalMenuPosition.x,
+                inputPoint.y - globalMenuPosition.y
+            )
+            if (splitScreenController.getSplitPosition(taskInfo.taskId)
+                == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+                // TODO(b/343561161): This also needs to be calculated differently if
+                //  the task is in top/bottom split.
+                val leftStageBounds = Rect()
+                splitScreenController.getStageBounds(leftStageBounds, Rect())
+                inputRelativeToMenu.x += leftStageBounds.width().toFloat()
+            }
+            return pointInView(
+                handleMenuViewContainer?.view,
+                inputRelativeToMenu.x,
+                inputRelativeToMenu.y
+            )
+        }
+    }
+
+    private fun pointInView(v: View?, x: Float, y: Float): Boolean {
+        return v != null && v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+    }
+
+    /**
+     * Check if the views for handle menu can be seen.
+     */
+    private fun viewsLaidOut(): Boolean = handleMenuViewContainer?.view?.isLaidOut ?: false
+
+    private fun loadHandleMenuDimensions() {
+        val resources = context.resources
+        menuWidth = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_width)
+        menuHeight = getHandleMenuHeight(resources)
+        marginMenuTop = loadDimensionPixelSize(
+            resources,
+            R.dimen.desktop_mode_handle_menu_margin_top
+        )
+        marginMenuStart = loadDimensionPixelSize(
+            resources,
+            R.dimen.desktop_mode_handle_menu_margin_start
+        )
+    }
+
+    /**
+     * Determines handle menu height based on if windowing pill should be shown.
+     */
+    private fun getHandleMenuHeight(resources: Resources): Int {
+        var menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height)
+        if (!shouldShowWindowingPill) {
+            menuHeight -= loadDimensionPixelSize(
+                resources,
+                R.dimen.desktop_mode_handle_menu_windowing_pill_height
+            )
+        }
+        if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+            menuHeight -= loadDimensionPixelSize(
+                resources,
+                R.dimen.desktop_mode_handle_menu_more_actions_pill_height
+            )
+        }
+        if (!shouldShowBrowserPill) {
+            menuHeight -= loadDimensionPixelSize(resources,
+                R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height)
+        }
+        return menuHeight
+    }
+
+    private fun loadDimensionPixelSize(resources: Resources, resourceId: Int): Int {
+        if (resourceId == Resources.ID_NULL) {
+            return 0
+        }
+        return resources.getDimensionPixelSize(resourceId)
+    }
+
+    fun close() {
+        val after = {
+            handleMenuViewContainer?.releaseView()
+            handleMenuViewContainer = null
+        }
+        if (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+            taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+            handleMenuAnimator?.animateCollapseIntoHandleClose(after)
+        } else {
+            handleMenuAnimator?.animateClose(after)
+        }
+    }
+
+    companion object {
+        private const val TAG = "HandleMenu"
+        private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 25a829b..e3d2234 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -108,7 +108,7 @@
      *
      * @param after runs after the animation finishes.
      */
-    fun animateCollapseIntoHandleClose(after: Runnable) {
+    fun animateCollapseIntoHandleClose(after: () -> Unit) {
         appInfoCollapseToHandle()
         animateAppInfoPillFadeOut()
         windowingPillClose()
@@ -125,7 +125,7 @@
      * @param after runs after animation finishes.
      *
      */
-    fun animateClose(after: Runnable) {
+    fun animateClose(after: () -> Unit) {
         appInfoPillCollapse()
         animateAppInfoPillFadeOut()
         windowingPillClose()
@@ -463,9 +463,9 @@
      *
      * @param after runs after animation finishes.
      */
-    private fun runAnimations(after: Runnable? = null) {
+    private fun runAnimations(after: (() -> Unit)? = null) {
         runningAnimation?.apply {
-            // Remove all listeners, so that after runnable isn't triggered upon cancel.
+            // Remove all listeners, so that the after function isn't triggered upon cancel.
             removeAllListeners()
             // If an animation runs while running animation is triggered, gracefully cancel.
             cancel()
@@ -475,7 +475,7 @@
             playTogether(animators)
             animators.clear()
             doOnEnd {
-                after?.run()
+                after?.invoke()
                 runningAnimation = null
             }
             start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 03dbbb3..d212f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
@@ -53,6 +54,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -105,7 +107,7 @@
      * System-wide context. Only used to create context with overridden configurations.
      */
     final Context mContext;
-    final DisplayController mDisplayController;
+    final @NonNull DisplayController mDisplayController;
     final ShellTaskOrganizer mTaskOrganizer;
     final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
     final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
@@ -158,7 +160,7 @@
 
     WindowDecoration(
             Context context,
-            DisplayController displayController,
+            @NonNull DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
             RunningTaskInfo taskInfo,
             @NonNull SurfaceControl taskSurface,
@@ -759,9 +761,12 @@
         }
 
         void addOrUpdate(WindowContainerTransaction wct) {
-            wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects);
+            final @InsetsSource.Flags int captionSourceFlags =
+                    Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
+            wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
+                    captionSourceFlags);
             wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
-                    mBoundingRects);
+                    mBoundingRects, 0 /* flags */);
         }
 
         void remove(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 6c2c8fd..4897f76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.PixelFormat
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.SurfaceControl
 import android.view.View
@@ -45,9 +46,11 @@
             WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
             WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
             PixelFormat.TRANSPARENT
-        )
-        lp.title = "Additional view container of Task=$taskId"
-        lp.setTrustedOverlay()
+        ).apply {
+            title = "Additional view container of Task=$taskId"
+            gravity = Gravity.LEFT or Gravity.TOP
+            setTrustedOverlay()
+        }
         val wm: WindowManager? = context.getSystemService(WindowManager::class.java)
         wm?.addView(view, lp)
     }
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 6cabbf9..4d1b6ba 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
@@ -311,6 +311,23 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+    val task1 = setUpFreeformTask(SECOND_DISPLAY)
+    val task2 = setUpFreeformTask(SECOND_DISPLAY)
+    markTaskHidden(task1)
+    markTaskHidden(task2)
+
+    controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+    val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+    assertThat(wct.hierarchyOps).hasSize(2)
+    // Expect order to be from bottom: task1, task2 (no wallpaper intent)
+    wct.assertReorderAt(index = 0, task1)
+    wct.assertReorderAt(index = 1, task2)
+  }
+
+  @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
     val homeTask = setUpHomeTask()
@@ -330,6 +347,22 @@
   }
 
   @Test
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+    val task1 = setUpFreeformTask(SECOND_DISPLAY)
+    val task2 = setUpFreeformTask(SECOND_DISPLAY)
+    markTaskHidden(task1)
+    markTaskHidden(task2)
+
+    controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+    val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+    assertThat(wct.hierarchyOps).hasSize(2)
+    wct.assertReorderAt(index = 0, task1)
+    wct.assertReorderAt(index = 1, task2)
+  }
+
+  @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
     val task1 = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 4dea5a7..6a94cd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -55,8 +55,6 @@
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
     private static final int EDGE_RESIZE_THICKNESS = 15;
-    private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
-            + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -91,14 +89,13 @@
                                 EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
                                 EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
-                .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
+                .addEqualityGroup(
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
-                .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
+                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                LARGE_CORNER_SIZE + 5),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
+                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                LARGE_CORNER_SIZE + 5))
                 .testEquals();
     }
 
@@ -124,21 +121,21 @@
     private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
         assertThat(region.contains(point.x, point.y)).isTrue();
         // Horizontally along the edge is still contained.
-        assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
-        assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
         // Vertically along the edge is not contained.
-        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
     }
 
     private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
         assertThat(region.contains(point.x, point.y)).isTrue();
         // Horizontally along the edge is not contained.
-        assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
-        assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
         // Vertically along the edge is contained.
-        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
     }
 
     /**
@@ -151,10 +148,7 @@
     public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
         Region region = new Region();
         GEOMETRY.union(region);
-        // Make sure we're choosing a point outside of any debug region buffer.
-        final int cornerRadius = DragResizeWindowGeometry.DEBUG
-                ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
-                : LARGE_CORNER_SIZE / 2;
+        final int cornerRadius = LARGE_CORNER_SIZE / 2;
 
         new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
     }
@@ -168,9 +162,7 @@
     public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
         Region region = new Region();
         GEOMETRY.union(region);
-        final int cornerRadius = DragResizeWindowGeometry.DEBUG
-                ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
-                : LARGE_CORNER_SIZE / 2;
+        final int cornerRadius = FINE_CORNER_SIZE / 2;
 
         new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 0c50ab6..e0e603ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -22,10 +22,10 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.graphics.Bitmap
 import android.graphics.Color
+import android.graphics.Point
 import android.graphics.Rect
-import android.platform.test.annotations.RequiresFlagsEnabled
-import android.platform.test.flag.junit.CheckFlagsRule
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display
@@ -33,6 +33,7 @@
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
 import android.view.View
+import androidx.core.graphics.toPointF
 import androidx.test.filters.SmallTest
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
@@ -46,6 +47,7 @@
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
@@ -69,7 +71,7 @@
 class HandleMenuTest : ShellTestCase() {
     @JvmField
     @Rule
-    val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+    val setFlagsRule: SetFlagsRule = SetFlagsRule()
 
     @Mock
     private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
@@ -100,7 +102,7 @@
         ) {
             SurfaceControl.Transaction()
         }
-        val menuView = LayoutInflater.from(context).inflate(
+        val menuView = LayoutInflater.from(mContext).inflate(
             R.layout.desktop_mode_window_decor_handle_menu, null)
         whenever(mockDesktopWindowDecoration.addWindow(
             anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
@@ -110,50 +112,60 @@
         whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
         whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
         whenever(displayLayout.isLandscape).thenReturn(true)
-        mockDesktopWindowDecoration.mDecorWindowContext = context
+        mContext.orCreateTestableResources.apply {
+            addOverride(R.dimen.desktop_mode_handle_menu_width, MENU_WIDTH)
+            addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
+            addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
+            addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
+        }
+        mockDesktopWindowDecoration.mDecorWindowContext = mContext
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+    @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testFullscreenMenuUsesSystemViewContainer() {
         createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED)
         val handleMenu = createAndShowHandleMenu()
-        assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+        assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of display.
-        assertTrue(handleMenu.mHandleMenuPosition.equals(16f, -512f))
+        val expected = Point(DISPLAY_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+        assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+    @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testFreeformMenu_usesViewHostViewContainer() {
         createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED)
         handleMenu = createAndShowHandleMenu()
-        assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalViewHostViewContainer)
+        assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer)
         // Verify menu is created near top-left of task.
-        assertTrue(handleMenu.mHandleMenuPosition.equals(12f, 8f))
+        val expected = Point(MENU_START_MARGIN, MENU_TOP_MARGIN)
+        assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+    @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testSplitLeftMenu_usesSystemViewContainer() {
         createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT)
         handleMenu = createAndShowHandleMenu()
-        assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+        assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
-        // show at the top of split left task.
-        assertTrue(handleMenu.mHandleMenuPosition.equals(-624f, -512f))
+        // show at the top-center of split left task.
+        val expected = Point(SPLIT_LEFT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+        assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+    @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testSplitRightMenu_usesSystemViewContainer() {
         createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT)
         handleMenu = createAndShowHandleMenu()
-        assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+        assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
-        // show at the top of split right task.
-        assertTrue(handleMenu.mHandleMenuPosition.equals(656f, -512f))
+        // show at the top-center of split right task.
+        val expected = Point(SPLIT_RIGHT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+        assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
     private fun createTaskInfo(windowingMode: Int, splitPosition: Int) {
@@ -178,14 +190,10 @@
             .setBounds(bounds)
             .setVisible(true)
             .build()
-        // Calculate captionX similar to how WindowDecoration calculates it.
-        whenever(mockDesktopWindowDecoration.captionX).thenReturn(
-            (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
-                .bounds.width() - context.resources.getDimensionPixelSize(
-                R.dimen.desktop_mode_fullscreen_decor_caption_width)) / 2)
         whenever(splitScreenController.getSplitPosition(any())).thenReturn(splitPosition)
         whenever(splitScreenController.getStageBounds(any(), any())).thenAnswer {
             (it.arguments.first() as Rect).set(SPLIT_LEFT_BOUNDS)
+            (it.arguments[1] as Rect).set(SPLIT_RIGHT_BOUNDS)
         }
     }
 
@@ -193,7 +201,7 @@
         val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
             R.layout.desktop_mode_app_header
         } else {
-            R.layout.desktop_mode_app_header
+            R.layout.desktop_mode_app_handle
         }
         val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
                 onClickListener, onTouchListener, appIcon, appName, displayController,
@@ -208,5 +216,9 @@
         private val FREEFORM_BOUNDS = Rect(500, 500, 2000, 1200)
         private val SPLIT_LEFT_BOUNDS = Rect(0, 0, 1280, 1600)
         private val SPLIT_RIGHT_BOUNDS = Rect(1280, 0, 2560, 1600)
+        private const val MENU_WIDTH = 200
+        private const val MENU_HEIGHT = 400
+        private const val MENU_TOP_MARGIN = 10
+        private const val MENU_START_MARGIN = 20
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 31c6479..2d1bf14 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,6 +18,8 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
@@ -54,6 +56,8 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
@@ -71,6 +75,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -80,6 +85,7 @@
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -105,6 +111,9 @@
     private static final int CORNER_RADIUS = 20;
     private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
 
@@ -260,7 +269,8 @@
                 eq(0 /* index */),
                 eq(WindowInsets.Type.captionBar()),
                 eq(new Rect(100, 300, 400, 364)),
-                any());
+                any(),
+                anyInt());
 
         verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
@@ -565,9 +575,9 @@
         windowDecor.relayout(taskInfo);
 
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(captionBar()), any(), any());
+                eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
     }
 
     @Test
@@ -654,9 +664,9 @@
 
         // Never added.
         verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(captionBar()), any(), any());
+                eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
         verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
         // No need to remove them if they were never added.
         verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token),
                 any(), eq(0) /* index */, eq(captionBar()));
@@ -681,9 +691,9 @@
         mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
         windowDecor.relayout(taskInfo);
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(captionBar()), any(), any());
+                eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
 
         windowDecor.close();
 
@@ -738,9 +748,9 @@
 
         // Insets should be applied twice.
         verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
-                eq(0) /* index */, eq(captionBar()), any(), any());
+                eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
         verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
-                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
     }
 
     @Test
@@ -765,9 +775,30 @@
 
         // Insets should only need to be applied once.
         verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
-                eq(0) /* index */, eq(captionBar()), any(), any());
+                eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
         verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
-                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+    public void testRelayout_captionInsetForceConsume() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+        final WindowContainerToken token = TestRunningTaskInfoBuilder.createMockWCToken();
+        final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setVisible(true);
+
+        final ActivityManager.RunningTaskInfo taskInfo =
+                builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+        windowDecor.relayout(taskInfo);
+
+        // Caption inset source should be force-consuming.
+        verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
+                eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
     }
 
     @Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 341599e..e302fa8 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -114,16 +114,12 @@
         "libbase",
         "libharfbuzz_ng",
         "libminikin",
-        "server_configurable_flags",
-        "libaconfig_storage_read_api_cc"
     ],
 
     static_libs: [
         "libui-types",
     ],
 
-    whole_static_libs: ["hwui_flags_cc_lib"],
-
     target: {
         android: {
             shared_libs: [
@@ -145,6 +141,8 @@
                 "libsync",
                 "libui",
                 "aconfig_text_flags_c_lib",
+                "server_configurable_flags",
+                "libaconfig_storage_read_api_cc",
             ],
             static_libs: [
                 "libEGL_blobCache",
@@ -155,6 +153,7 @@
                 "libstatssocket_lazy",
                 "libtonemap",
             ],
+            whole_static_libs: ["hwui_flags_cc_lib"],
         },
         host: {
             static_libs: [
@@ -419,7 +418,6 @@
     ],
 
     static_libs: [
-        "libnativehelper_lazy",
         "libziparchive_for_incfs",
     ],
 
@@ -446,6 +444,7 @@
             ],
             static_libs: [
                 "libgif",
+                "libnativehelper_lazy",
                 "libstatslog_hwui",
                 "libstatspull_lazy",
                 "libstatssocket_lazy",
@@ -464,6 +463,7 @@
             ],
             static_libs: [
                 "libandroidfw",
+                "libnativehelper_jvm",
             ],
         },
     },
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 70a9ef0..073bc8d 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -41,6 +41,7 @@
 extern int register_android_graphics_RenderEffect(JNIEnv* env);
 extern int register_android_graphics_Typeface(JNIEnv* env);
 extern int register_android_graphics_YuvImage(JNIEnv* env);
+extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env);
 
 namespace android {
 
@@ -51,6 +52,8 @@
 extern int register_android_graphics_ColorSpace(JNIEnv* env);
 extern int register_android_graphics_DrawFilter(JNIEnv* env);
 extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_Gainmap(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
 extern int register_android_graphics_Matrix(JNIEnv* env);
 extern int register_android_graphics_Paint(JNIEnv* env);
 extern int register_android_graphics_Path(JNIEnv* env);
@@ -72,6 +75,7 @@
 extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 
 #define REG_JNI(name)      { name }
 struct RegJNIRec {
@@ -95,7 +99,11 @@
          REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
         {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
         {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
+        {"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)},
         {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+        {"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)},
+        {"android.graphics.HardwareRendererObserver",
+         REG_JNI(register_android_graphics_HardwareRendererObserver)},
         {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
         {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
         {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
@@ -118,6 +126,8 @@
          REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
         {"android.graphics.animation.RenderNodeAnimator",
          REG_JNI(register_android_graphics_animation_RenderNodeAnimator)},
+        {"android.graphics.drawable.AnimatedImageDrawable",
+         REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable)},
         {"android.graphics.drawable.AnimatedVectorDrawable",
          REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
         {"android.graphics.drawable.VectorDrawable",
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index cfca480..0efb2c8 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -17,7 +17,6 @@
 #include <SkFontMetrics.h>
 #include <SkRRect.h>
 #include <SkTextBlob.h>
-#include <com_android_graphics_hwui_flags.h>
 
 #include "../utils/Color.h"
 #include "Canvas.h"
@@ -30,7 +29,19 @@
 #include "hwui/PaintFilter.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
 namespace flags = com::android::graphics::hwui::flags;
+#else
+namespace flags {
+constexpr bool high_contrast_text_luminance() {
+    return false;
+}
+constexpr bool high_contrast_text_small_text_rect() {
+    return false;
+}
+}  // namespace flags
+#endif
 
 namespace android {
 
diff --git a/location/Android.bp b/location/Android.bp
index c0e102a..bc02d1f 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -30,9 +30,6 @@
         "app-compat-annotations",
         "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
     ],
-    hidden_api_packages: [
-        "com.android.internal.location",
-    ],
     aidl: {
         include_dirs: [
             "frameworks/base/location/java",
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/location/java/com/android/internal/location/package-info.java
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to location/java/com/android/internal/location/package-info.java
index c92234c..25573c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/location/java/com/android/internal/location/package-info.java
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+/**
+ * Exclude from API surfaces
+ *
+ * @hide
+ */
+package com.android.internal.location;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 73deb17..03cd535 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1797,6 +1797,7 @@
                 (flags != 0  // cannot have any special flags
                 || attributes.getUsage() != AudioAttributes.USAGE_MEDIA
                 || (attributes.getContentType() != AudioAttributes.CONTENT_TYPE_UNKNOWN
+                    && attributes.getContentType() != AudioAttributes.CONTENT_TYPE_SPEECH
                     && attributes.getContentType() != AudioAttributes.CONTENT_TYPE_MUSIC
                     && attributes.getContentType() != AudioAttributes.CONTENT_TYPE_MOVIE))) {
             return false;
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 13ac231..0282e6f 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -61,9 +61,6 @@
         "android.nfc",
         "com.android.nfc",
     ],
-    hidden_api_packages: [
-        "com.android.nfc",
-    ],
     impl_library_visibility: [
         "//frameworks/base:__subpackages__",
         "//cts/hostsidetests/multidevices/nfc:__subpackages__",
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 9ce1c82..395f81d 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -963,22 +963,9 @@
             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
                     + " NFC extras APIs");
         }
-        try {
-            return sService.getNfcDtaInterface(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcDtaInterface(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->  sService.getNfcDtaInterface(mContext.getPackageName()),
+                null);
+
     }
 
     /**
@@ -1095,22 +1082,8 @@
     @SystemApi
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public @AdapterState int getAdapterState() {
-        try {
-            return sService.getState();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return NfcAdapter.STATE_OFF;
-            }
-            try {
-                return sService.getState();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return NfcAdapter.STATE_OFF;
-        }
+        return callServiceReturn(() ->  sService.getState(), NfcAdapter.STATE_OFF);
+
     }
 
     /**
@@ -1134,22 +1107,8 @@
     @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean enable() {
-        try {
-            return sService.enable(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enable(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.enable(mContext.getPackageName()), false);
+
     }
 
     /**
@@ -1175,22 +1134,9 @@
     @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean disable() {
-        try {
-            return sService.disable(true, mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.disable(true, mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.disable(true, mContext.getPackageName()),
+                false);
+
     }
 
     /**
@@ -1200,22 +1146,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean disable(boolean persist) {
-        try {
-            return sService.disable(persist, mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.disable(persist, mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.disable(persist, mContext.getPackageName()),
+                false);
+
     }
 
     /**
@@ -1241,12 +1174,7 @@
      */
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
     public boolean isObserveModeSupported() {
-        try {
-            return sService.isObserveModeSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isObserveModeSupported(), false);
     }
 
     /**
@@ -1257,12 +1185,7 @@
 
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
     public boolean isObserveModeEnabled() {
-        try {
-            return sService.isObserveModeEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isObserveModeEnabled(), false);
     }
 
     /**
@@ -1286,12 +1209,8 @@
             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
                     + " observe mode APIs");
         }
-        try {
-            return sService.setObserveMode(enabled, mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setObserveMode(enabled, mContext.getPackageName()),
+                false);
     }
 
     /**
@@ -2057,22 +1976,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setNfcSecure(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setNfcSecure(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setNfcSecure(enable), false);
+
     }
 
     /**
@@ -2088,22 +1993,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.deviceSupportsNfcSecure();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.deviceSupportsNfcSecure();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.deviceSupportsNfcSecure(), false);
+
     }
 
     /**
@@ -2121,22 +2012,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.getNfcAntennaInfo();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcAntennaInfo();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->  sService.getNfcAntennaInfo(), null);
+
     }
 
     /**
@@ -2154,22 +2031,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isNfcSecureEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isNfcSecureEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isNfcSecureEnabled(), false);
+
     }
 
     /**
@@ -2185,22 +2048,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.enableReaderOption(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enableReaderOption(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.enableReaderOption(enable), false);
+
     }
 
     /**
@@ -2214,22 +2063,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isReaderOptionSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isReaderOptionSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isReaderOptionSupported(), false);
+
     }
 
     /**
@@ -2245,22 +2080,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isReaderOptionEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isReaderOptionEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isReaderOptionEnabled(), false);
+
     }
 
     /**
@@ -2388,11 +2209,9 @@
         synchronized (mLock) {
             mTagRemovedListener = iListener;
         }
-        try {
-            return sService.ignore(tag.getServiceHandle(), debounceMs, iListener);
-        } catch (RemoteException e) {
-            return false;
-        }
+        final ITagRemovedCallback.Stub passedListener = iListener;
+        return callServiceReturn(() ->
+                sService.ignore(tag.getServiceHandle(), debounceMs, passedListener), false);
     }
 
     /**
@@ -2509,22 +2328,9 @@
             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
                     + " NFC extras APIs");
         }
-        try {
-            return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->
+                sService.getNfcAdapterExtrasInterface(mContext.getPackageName()), null);
+
     }
 
     void enforceResumed(Activity activity) {
@@ -2569,22 +2375,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setControllerAlwaysOn(value);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setControllerAlwaysOn(value);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setControllerAlwaysOn(value), false);
+
     }
 
     /**
@@ -2600,22 +2392,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
     public boolean isControllerAlwaysOn() {
-        try {
-            return sService.isControllerAlwaysOn();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isControllerAlwaysOn();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isControllerAlwaysOn(), false);
+
     }
 
     /**
@@ -2634,22 +2412,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isControllerAlwaysOnSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isControllerAlwaysOnSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isControllerAlwaysOnSupported(), false);
+
     }
 
     /**
@@ -2719,21 +2483,9 @@
             Log.e(TAG, "TagIntentAppPreference is not supported");
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            try {
-                return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE;
-        }
+        return callServiceReturn(() ->
+                sService.setTagIntentAppPreferenceForUser(userId, pkg, allow),
+                        TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE);
     }
 
 
@@ -2808,22 +2560,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isTagIntentAppPreferenceSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isTagIntentAppPreferenceSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isTagIntentAppPreferenceSupported(), false);
+
     }
 
    /**
@@ -2836,26 +2574,10 @@
     @TestApi
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
-        try {
-            if (sService == null) {
-                attemptDeadServiceRecovery(null);
-            }
-            sService.notifyPollingLoop(pollingFrame);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return;
-            }
-            try {
-                sService.notifyPollingLoop(pollingFrame);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-        }
+        callService(() ->  sService.notifyPollingLoop(pollingFrame));
     }
 
+
    /**
      * Notifies the system of new HCE data for tests.
      *
@@ -2863,11 +2585,19 @@
      */
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void notifyTestHceData(int technology, byte[] data) {
+        callService(() ->  sService.notifyTestHceData(technology, data));
+    }
+
+    interface ServiceCall {
+        void call() throws RemoteException;
+    }
+
+    void callService(ServiceCall call) {
         try {
             if (sService == null) {
                 attemptDeadServiceRecovery(null);
             }
-            sService.notifyTestHceData(technology, data);
+            call.call();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -2876,12 +2606,36 @@
                 return;
             }
             try {
-                sService.notifyTestHceData(technology, data);
-            } catch (RemoteException e2) {
+                call.call();
+            } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
         }
     }
+    interface ServiceCallReturn<T> {
+        T call() throws RemoteException;
+    }
+    <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+        try {
+            if (sService == null) {
+                attemptDeadServiceRecovery(null);
+            }
+            return call.call();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return defaultReturn;
+            }
+            try {
+                return call.call();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+        return defaultReturn;
+    }
 
    /**
      * Notifies the system of a an HCE session being deactivated.
@@ -2891,24 +2645,7 @@
     @TestApi
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void notifyHceDeactivated() {
-        try {
-            if (sService == null) {
-                attemptDeadServiceRecovery(null);
-            }
-            sService.notifyHceDeactivated();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return;
-            }
-            try {
-                sService.notifyHceDeactivated();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-        }
+        callService(() ->  sService.notifyHceDeactivated());
     }
 
     /**
@@ -2924,22 +2661,7 @@
         if (!sHasNfcWlcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setWlcEnabled(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setWlcEnabled(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setWlcEnabled(enable), false);
     }
 
     /**
@@ -2954,22 +2676,8 @@
         if (!sHasNfcWlcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isWlcEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isWlcEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isWlcEnabled(), false);
+
     }
 
     /**
@@ -3048,22 +2756,8 @@
         if (!sHasNfcWlcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.getWlcListenerDeviceInfo();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getWlcListenerDeviceInfo();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->  sService.getWlcListenerDeviceInfo(), null);
+
     }
 
     /**
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 225f8c6..52e0cbb 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -31,3 +31,11 @@
     description: "Deletes flag and settings resets"
     bug: "333847376"
 }
+
+flag {
+    name: "refactor_crashrecovery"
+    namespace: "modularization"
+    description: "Refactor required CrashRecovery code"
+    bug: "289203818"
+    is_fixed_read_only: true
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index c260426..88770d4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -206,7 +206,7 @@
             return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
         }
 
-        val restriction = getDevicePolicyRestrictions()
+        val restriction = getDevicePolicyRestrictions(isTrustedSource)
         if (restriction != null) {
             val adminSupportDetailsIntent =
                 devicePolicyManager!!.createAdminSupportIntent(restriction)
@@ -237,18 +237,25 @@
         intent: Intent,
         callingUid: Int,
     ): Boolean {
-        val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
-        return (sourceInfo != null && sourceInfo.isPrivilegedApp
-            && (isNotUnknownSource
-            || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)))
+        val isPrivilegedAndKnown = sourceInfo != null && sourceInfo.isPrivilegedApp &&
+            intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
+        val isInstallPkgPermissionGranted =
+            isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)
+
+        return isPrivilegedAndKnown || isInstallPkgPermissionGranted
     }
 
-    private fun getDevicePolicyRestrictions(): String? {
-        val restrictions = arrayOf(
-            UserManager.DISALLOW_INSTALL_APPS,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
-        )
+    private fun getDevicePolicyRestrictions(isTrustedSource: Boolean): String? {
+        val restrictions: Array<String> = if (isTrustedSource) {
+            arrayOf(UserManager.DISALLOW_INSTALL_APPS)
+        } else {
+            arrayOf(
+                UserManager.DISALLOW_INSTALL_APPS,
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+            )
+        }
+
         for (restriction in restrictions) {
             if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
                 continue
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
index 8572852..828a95f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -130,8 +130,8 @@
      * @param context the [Context] object
      * @param callingUid the UID of the caller of Pia
      * @param isTrustedSource indicates whether install request is coming from a privileged app
-     * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or that has
-     * the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
+     * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or an app that
+     * has the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
      *
      * @return `true` if the package is either a system downloads provider, a document manager,
      * a trusted source, or has declared the
diff --git a/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml b/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml
index 6552296..bc5ec69 100644
--- a/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml
+++ b/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml
@@ -28,7 +28,7 @@
         android:layout_height="match_parent"
         android:orientation="vertical">
 
-        <com.android.settingslib.notification.ZenRadioLayout
+        <com.android.settingslib.notification.modes.ZenRadioLayout
             android:id="@+id/zen_duration_conditions"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -46,7 +46,7 @@
                 android:layout_width="fill_parent"
                 android:layout_height="fill_parent"
                 android:orientation="vertical"/>
-        </com.android.settingslib.notification.ZenRadioLayout>
+        </com.android.settingslib.notification.modes.ZenRadioLayout>
     </LinearLayout>
 
 </ScrollView>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index f89fe93..67139b5 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -14,8 +14,7 @@
      limitations under the License.
 -->
 
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources>
     <color name="disabled_text_color">#66000000</color> <!-- 38% black -->
 
     <color name="bt_color_icon_1">#b4a50e0e</color> <!-- 72% Material Red 900 -->
@@ -34,8 +33,8 @@
     <color name="bt_color_bg_6">#e9d2fd</color> <!-- Material Purple 100 -->
     <color name="bt_color_bg_7">#cbf0f8</color> <!-- Material Cyan 100 -->
 
-    <color name="black">@*android:color/black</color>
-    <color name="white">@*android:color/white</color>
+    <color name="dark_mode_icon_color_single_tone">#99000000</color>
+    <color name="light_mode_icon_color_single_tone">#ffffff</color>
 
     <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 6c2bd41..ef0f6cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -100,9 +100,9 @@
         mCutoutHeightFraction = context.getResources().getFloat(
                 com.android.internal.R.dimen.config_signalCutoutHeightFraction);
         mDarkModeFillColor = Utils.getColorStateListDefaultColor(context,
-                R.color.black);
+                R.color.dark_mode_icon_color_single_tone);
         mLightModeFillColor = Utils.getColorStateListDefaultColor(context,
-                R.color.white);
+                R.color.light_mode_icon_color_single_tone);
         mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
         mTransparentPaint.setColor(context.getColor(android.R.color.transparent));
         mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 6571dd7..eced7b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -29,6 +29,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -41,7 +42,6 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -65,7 +65,9 @@
                 refreshDevices();
             };
 
-    private final AtomicReference<MediaRouter2.ScanToken> mScanToken = new AtomicReference<>();
+    @GuardedBy("this")
+    @Nullable
+    private MediaRouter2.ScanToken mScanToken;
 
     // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
     /* package */ RouterInfoMediaManager(
@@ -101,8 +103,13 @@
     @Override
     protected void startScanOnRouter() {
         if (Flags.enableScreenOffScanning()) {
-            MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build();
-            mScanToken.compareAndSet(null, mRouter.requestScan(request));
+            synchronized (this) {
+                if (mScanToken == null) {
+                    MediaRouter2.ScanRequest request =
+                            new MediaRouter2.ScanRequest.Builder().build();
+                    mScanToken = mRouter.requestScan(request);
+                }
+            }
         } else {
             mRouter.startScan();
         }
@@ -120,9 +127,11 @@
     @Override
     protected void stopScanOnRouter() {
         if (Flags.enableScreenOffScanning()) {
-            MediaRouter2.ScanToken token = mScanToken.getAndSet(null);
-            if (token != null) {
-                mRouter.cancelScanRequest(token);
+            synchronized (this) {
+                if (mScanToken != null) {
+                    mRouter.cancelScanRequest(mScanToken);
+                    mScanToken = null;
+                }
             }
         } else {
             mRouter.stopScan();
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 273a63d..72c3c17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -56,7 +56,7 @@
     val backgroundHandler: Handler?,
 ) : ZenModeRepository {
 
-    private val notificationBroadcasts =
+    private val notificationBroadcasts by lazy {
         callbackFlow {
                 val receiver =
                     object : BroadcastReceiver() {
@@ -95,8 +95,9 @@
                     )
                 }
             }
+    }
 
-    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> =
+    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
         if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
             flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
                 notificationManager.consolidatedNotificationPolicy
@@ -105,11 +106,13 @@
             flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
                 notificationManager.consolidatedNotificationPolicy
             }
+    }
 
-    override val globalZenMode: StateFlow<Int?> =
+    override val globalZenMode: StateFlow<Int?> by lazy {
         flowFromBroadcast(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) {
             notificationManager.zenMode
         }
+    }
 
     private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
         notificationBroadcasts
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 2cc9112..03a2b75 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -286,11 +286,7 @@
                 mRule, oldCondition, conditionId));
     }
 
-    public boolean canEditName() {
-        return !isManualDnd();
-    }
-
-    public boolean canEditIcon() {
+    public boolean canEditNameAndIcon() {
         return !isManualDnd();
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 096c25d..06333b61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -48,7 +48,6 @@
 @RunWith(RobolectricTestRunner::class)
 @SmallTest
 class ZenModeRepositoryTest {
-
     @Mock private lateinit var context: Context
 
     @Mock private lateinit var notificationManager: NotificationManager
@@ -73,7 +72,7 @@
             )
     }
 
-    @DisableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+    @DisableFlags(Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
     @Test
     fun consolidatedPolicyChanges_repositoryEmits_flagsOff() {
         testScope.runTest {
@@ -88,9 +87,7 @@
             triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
             runCurrent()
 
-            assertThat(values)
-                .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
-                .inOrder()
+            assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
         }
     }
 
@@ -109,9 +106,7 @@
             triggerIntent(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED)
             runCurrent()
 
-            assertThat(values)
-                .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
-                .inOrder()
+            assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
         }
     }
 
@@ -128,14 +123,13 @@
             runCurrent()
 
             assertThat(values)
-                .containsExactlyElementsIn(
-                    listOf(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS))
+                .containsExactly(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS)
                 .inOrder()
         }
     }
 
     private fun triggerIntent(action: String) {
-        verify(context).registerReceiver(receiverCaptor.capture(), any())
+        verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
         receiverCaptor.value.onReceive(context, Intent(action))
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index 9e54312..e705f97 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -56,12 +56,14 @@
         assertThat(zenMode.getId()).isEqualTo("id");
         assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE);
         assertThat(zenMode.isManualDnd()).isFalse();
+        assertThat(zenMode.canEditNameAndIcon()).isTrue();
         assertThat(zenMode.canBeDeleted()).isTrue();
         assertThat(zenMode.isActive()).isTrue();
 
         ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false);
         assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
         assertThat(manualMode.isManualDnd()).isTrue();
+        assertThat(manualMode.canEditNameAndIcon()).isFalse();
         assertThat(manualMode.canBeDeleted()).isFalse();
         assertThat(manualMode.isActive()).isFalse();
     }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9cbb1bd..8b33afe 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -789,7 +789,11 @@
         "android.test.mock",
         "keepanno-annotations",
     ],
-    kotlincflags: ["-Xjvm-default=all"],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        // TODO(b/352363800): Why do we need this?
+        "-J-Xmx8192M",
+    ],
     aaptflags: [
         "--extra-packages",
         "com.android.systemui",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e2ecda3..28e88d2 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -591,6 +591,16 @@
 }
 
 flag {
+    name: "screenshot_save_image_exporter"
+    namespace: "systemui"
+    description: "Save all screenshots using ImageExporter"
+    bug: "352308052"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
    name: "run_fingerprint_detect_on_dismissible_keyguard"
    namespace: "systemui"
    description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -1143,3 +1153,13 @@
        purpose: PURPOSE_BUGFIX
    }
 }
+
+flag {
+  namespace: "systemui"
+  name: "qs_register_setting_observer_on_bg_thread"
+  description: "Registers Quick Settings content providers on background thread"
+  bug: "351766769"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index b8f9ca8..f655ac1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -83,6 +83,7 @@
 import com.android.compose.PlatformButton
 import com.android.compose.animation.Easings
 import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -516,13 +517,22 @@
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
-    SceneTransitionLayout(
-        currentScene = currentSceneKey,
-        onChangeScene = {},
-        transitions = SceneTransitions,
-        modifier = modifier,
-        enableInterruptions = false,
-    ) {
+    val state = remember {
+        MutableSceneTransitionLayoutState(
+            currentSceneKey,
+            SceneTransitions,
+            enableInterruptions = false,
+        )
+    }
+
+    // Update state whenever currentSceneKey has changed.
+    LaunchedEffect(state, currentSceneKey) {
+        if (currentSceneKey != state.transitionState.currentScene) {
+            state.setTargetScene(currentSceneKey, coroutineScope = this)
+        }
+    }
+
+    SceneTransitionLayout(state, modifier = modifier) {
         scene(SceneKeys.ContiguousSceneKey) {
             FoldableScene(
                 aboveFold = aboveFold,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 0a4c6fd..7400711 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -67,8 +67,7 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val shouldUseSplitNotificationShade by
-            viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+        val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
         val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
 
         LockscreenLongPress(
@@ -100,7 +99,7 @@
                                         }
                                 )
                             }
-                            if (shouldUseSplitNotificationShade) {
+                            if (isShadeLayoutWide) {
                                 with(notificationSection) {
                                     Notifications(
                                         burnInParams = null,
@@ -112,7 +111,7 @@
                                 }
                             }
                         }
-                        if (!shouldUseSplitNotificationShade) {
+                        if (!isShadeLayoutWide) {
                             with(notificationSection) {
                                 Notifications(
                                     burnInParams = null,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 065f2a2..72cf832 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -69,8 +69,7 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
-        val shouldUseSplitNotificationShade by
-            viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+        val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
         val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
 
         LockscreenLongPress(
@@ -102,7 +101,7 @@
                                         },
                                 )
                             }
-                            if (shouldUseSplitNotificationShade) {
+                            if (isShadeLayoutWide) {
                                 with(notificationSection) {
                                     Notifications(
                                         burnInParams = null,
@@ -114,7 +113,7 @@
                                 }
                             }
                         }
-                        if (!shouldUseSplitNotificationShade) {
+                        if (!isShadeLayoutWide) {
                             with(notificationSection) {
                                 Notifications(
                                     burnInParams = null,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 7f80dfa..a1f2042 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -90,8 +90,8 @@
      */
     @Composable
     fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
-        val shouldUseSplitNotificationShade by
-            lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+        val isShadeLayoutWide by
+            lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
         val areNotificationsVisible by
             lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
         val splitShadeTopMargin: Dp =
@@ -111,9 +111,7 @@
             modifier =
                 modifier
                     .fillMaxWidth()
-                    .thenIf(shouldUseSplitNotificationShade) {
-                        Modifier.padding(top = splitShadeTopMargin)
-                    }
+                    .thenIf(isShadeLayoutWide) { Modifier.padding(top = splitShadeTopMargin) }
                     .let {
                         if (burnInParams == null) {
                             it
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 0673153..0cd4b68 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -26,6 +26,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
@@ -33,6 +34,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.thenIf
@@ -78,13 +80,22 @@
                     WeatherClockScenes.splitShadeLargeClockScene
             }
 
-        SceneTransitionLayout(
-            modifier = modifier,
-            currentScene = currentScene,
-            onChangeScene = {},
-            transitions = ClockTransition.defaultClockTransitions,
-            enableInterruptions = false,
-        ) {
+        val state = remember {
+            MutableSceneTransitionLayoutState(
+                currentScene,
+                ClockTransition.defaultClockTransitions,
+                enableInterruptions = false,
+            )
+        }
+
+        // Update state whenever currentSceneKey has changed.
+        LaunchedEffect(state, currentScene) {
+            if (currentScene != state.transitionState.currentScene) {
+                state.setTargetScene(currentScene, coroutineScope = this)
+            }
+        }
+
+        SceneTransitionLayout(state, modifier) {
             scene(splitShadeLargeClockScene) {
                 LargeClockWithSmartSpace(
                     shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 581f3a5..d629eec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.media.controls.ui.composable
 
+import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
@@ -26,7 +28,6 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.contains
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -36,7 +37,8 @@
 
 private object MediaCarousel {
     object Elements {
-        internal val Content = ElementKey("MediaCarouselContent")
+        internal val Content =
+            ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker)
     }
 }
 
@@ -61,40 +63,43 @@
     mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight)
     carouselController.setSceneContainerSize(layoutWidth, layoutHeight)
 
-    AndroidView(
-        modifier =
-            modifier
-                .element(MediaCarousel.Elements.Content)
-                .height(mediaHeight)
-                .fillMaxWidth()
-                .layout { measurable, constraints ->
-                    val placeable = measurable.measure(constraints)
+    MovableElement(
+        key = MediaCarousel.Elements.Content,
+        modifier = modifier.height(mediaHeight).fillMaxWidth()
+    ) {
+        content {
+            AndroidView(
+                modifier =
+                    Modifier.fillMaxSize().layout { measurable, constraints ->
+                        val placeable = measurable.measure(constraints)
 
-                    // Notify controller to size the carousel for the current space
-                    mediaHost.measurementInput = MeasurementInput(placeable.width, placeable.height)
-                    carouselController.setSceneContainerSize(placeable.width, placeable.height)
+                        // Notify controller to size the carousel for the current space
+                        mediaHost.measurementInput =
+                            MeasurementInput(placeable.width, placeable.height)
+                        carouselController.setSceneContainerSize(placeable.width, placeable.height)
 
-                    layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+                        layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+                    },
+                factory = { context ->
+                    FrameLayout(context).apply {
+                        layoutParams =
+                            FrameLayout.LayoutParams(
+                                FrameLayout.LayoutParams.MATCH_PARENT,
+                                FrameLayout.LayoutParams.MATCH_PARENT,
+                            )
+                    }
                 },
-        factory = { context ->
-            FrameLayout(context).apply {
-                val mediaFrame = carouselController.mediaFrame
-                (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
-                addView(mediaFrame)
-                layoutParams =
-                    FrameLayout.LayoutParams(
-                        FrameLayout.LayoutParams.MATCH_PARENT,
-                        FrameLayout.LayoutParams.MATCH_PARENT,
-                    )
-            }
-        },
-        update = {
-            if (it.contains(carouselController.mediaFrame)) {
-                return@AndroidView
-            }
-            val mediaFrame = carouselController.mediaFrame
-            (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
-            it.addView(mediaFrame)
-        },
-    )
+                update = { it.setView(carouselController.mediaFrame) },
+                onRelease = { it.removeAllViews() }
+            )
+        }
+    }
+}
+
+private fun ViewGroup.setView(view: View) {
+    if (view.parent == this) {
+        return
+    }
+    (view.parent as? ViewGroup)?.removeView(view)
+    addView(view)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
new file mode 100644
index 0000000..0398133
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.media.controls.ui.composable
+
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementScenePicker
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+import com.android.systemui.scene.shared.model.Scenes
+
+/** [ElementScenePicker] implementation for the media carousel object. */
+object MediaScenePicker : ElementScenePicker {
+
+    private val shadeLockscreenFraction = 0.65f
+    private val scenes =
+        setOf(
+            Scenes.Lockscreen,
+            Scenes.Shade,
+            Scenes.QuickSettings,
+            Scenes.QuickSettingsShade,
+            Scenes.Communal
+        )
+
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey? {
+        return when {
+            // TODO: 352052894 - update with the actual scene picking
+            transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
+                if (transition.progress < shadeLockscreenFraction) {
+                    Scenes.Lockscreen
+                } else {
+                    Scenes.Shade
+                }
+            }
+
+            // TODO: 345467290 - update with the actual scene picking
+            transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
+                if (transition.progress < 1f - shadeLockscreenFraction) {
+                    Scenes.Shade
+                } else {
+                    Scenes.Lockscreen
+                }
+            }
+
+            // TODO: 345467290 - update with the actual scene picking
+            transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
+                Scenes.QuickSettings
+            }
+
+            // TODO: 340216785 - update with the actual scene picking
+            else -> pickSingleSceneIn(scenes, transition, element)
+        }
+    }
+}
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 4a6599a..805351e 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
@@ -34,9 +34,11 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.navigationBarsPadding
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
@@ -51,6 +53,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.CompositingStrategy
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
@@ -60,6 +63,7 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
 import com.android.compose.animation.scene.UserAction
@@ -255,6 +259,8 @@
     val mediaOffset by
         animateSceneDpAsState(value = InQQS, key = MediaLandscapeTopOffset, canOverflow = false)
 
+    val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
+
     Box(
         modifier =
             modifier.thenIf(shouldPunchHoleBehindScrim) {
@@ -358,11 +364,22 @@
                 notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
             }
         }
-        NotificationStackCutoffGuideline(
-            stackScrollView = notificationStackScrollView,
-            viewModel = notificationsPlaceholderViewModel,
-            modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding()
-        )
+        Box(
+            modifier =
+                Modifier.align(Alignment.BottomCenter)
+                    .height(navBarHeight)
+                    .pointerInteropFilter { true }
+                    .verticalNestedScrollToScene(
+                        topBehavior = NestedScrollBehavior.EdgeAlways,
+                        isExternalOverscrollGesture = { false }
+                    )
+        ) {
+            NotificationStackCutoffGuideline(
+                stackScrollView = notificationStackScrollView,
+                viewModel = notificationsPlaceholderViewModel,
+                modifier = Modifier.align(Alignment.TopCenter)
+            )
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
deleted file mode 100644
index f514ab4..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.ui.composable
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun StatusBar(
-    modifier: Modifier = Modifier,
-) {
-    // TODO(b/272780101): implement.
-    Row(
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
-        horizontalArrangement = Arrangement.Center,
-        verticalAlignment = Alignment.CenterVertically,
-    ) {
-        Text("Status bar")
-    }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index cdcfc84..615d393 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -241,43 +241,50 @@
                 }
             }
 
-            awaitPointerEventScope {
-                while (isActive) {
-                    try {
-                        detectDragGestures(
-                            orientation = orientation,
-                            startDragImmediately = startDragImmediately,
-                            onDragStart = { startedPosition, overSlop, pointersDown ->
-                                velocityTracker.resetTracking()
-                                onDragStarted(startedPosition, overSlop, pointersDown)
-                            },
-                            onDrag = { controller, change, amount ->
-                                velocityTracker.addPointerInputChange(change)
-                                controller.onDrag(amount)
-                            },
-                            onDragEnd = { controller ->
-                                val viewConfiguration = currentValueOf(LocalViewConfiguration)
-                                val maxVelocity =
-                                    viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
-                                val velocity = velocityTracker.calculateVelocity(maxVelocity)
-                                controller.onStop(
-                                    velocity =
-                                        when (orientation) {
-                                            Orientation.Horizontal -> velocity.x
-                                            Orientation.Vertical -> velocity.y
-                                        },
-                                    canChangeScene = true,
-                                )
-                            },
-                            onDragCancel = { controller ->
-                                controller.onStop(velocity = 0f, canChangeScene = true)
-                            },
-                            swipeDetector = swipeDetector
-                        )
-                    } catch (exception: CancellationException) {
-                        // If the coroutine scope is active, we can just restart the drag cycle.
-                        if (!isActive) {
-                            throw exception
+            // The order is important here: we want to make sure that the previous PointerEventScope
+            // is initialized first. This ensures that the following PointerEventScope doesn't
+            // receive more events than the first one.
+            launch {
+                awaitPointerEventScope {
+                    while (isActive) {
+                        try {
+                            detectDragGestures(
+                                orientation = orientation,
+                                startDragImmediately = startDragImmediately,
+                                onDragStart = { startedPosition, overSlop, pointersDown ->
+                                    velocityTracker.resetTracking()
+                                    onDragStarted(startedPosition, overSlop, pointersDown)
+                                },
+                                onDrag = { controller, change, amount ->
+                                    velocityTracker.addPointerInputChange(change)
+                                    controller.onDrag(amount)
+                                },
+                                onDragEnd = { controller ->
+                                    val viewConfiguration = currentValueOf(LocalViewConfiguration)
+                                    val maxVelocity =
+                                        viewConfiguration.maximumFlingVelocity.let {
+                                            Velocity(it, it)
+                                        }
+                                    val velocity = velocityTracker.calculateVelocity(maxVelocity)
+                                    controller.onStop(
+                                        velocity =
+                                            when (orientation) {
+                                                Orientation.Horizontal -> velocity.x
+                                                Orientation.Vertical -> velocity.y
+                                            },
+                                        canChangeScene = true,
+                                    )
+                                },
+                                onDragCancel = { controller ->
+                                    controller.onStop(velocity = 0f, canChangeScene = true)
+                                },
+                                swipeDetector = swipeDetector
+                            )
+                        } catch (exception: CancellationException) {
+                            // If the coroutine scope is active, we can just restart the drag cycle.
+                            if (!isActive) {
+                                throw exception
+                            }
                         }
                     }
                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index dfb8c49..734241e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -56,13 +56,7 @@
             progress.collect { backEvent -> transition.dragProgress = backEvent.progress }
 
             // Back gesture successful.
-            transition.animateTo(
-                if (state.canChangeScene(targetSceneForBack)) {
-                    targetSceneForBack
-                } else {
-                    fromScene
-                }
-            )
+            transition.animateTo(targetSceneForBack)
         } catch (e: CancellationException) {
             // Back gesture cancelled.
             transition.animateTo(fromScene)
@@ -105,12 +99,15 @@
             return it
         }
 
-        currentScene = scene
+        if (scene != currentScene && state.transitionState == this && state.canChangeScene(scene)) {
+            currentScene = scene
+        }
+
         val targetProgress =
-            when (scene) {
+            when (currentScene) {
                 fromScene -> 0f
                 toScene -> 1f
-                else -> error("scene $scene should be either $fromScene or $toScene")
+                else -> error("scene $currentScene should be either $fromScene or $toScene")
             }
 
         val animatable = Animatable(dragProgress).also { progressAnimatable = it }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
deleted file mode 100644
index b346a70..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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.compose.animation.scene
-
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.drawOutline
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
-
-/**
- * Punch a hole in this node with the given [size], [offset] and [shape].
- *
- * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
- * This can be used to make content drawn below an opaque element visible. For example, if we have
- * [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
- * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big clock
- * time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be the
- * result.
- */
-@Stable
-fun Modifier.punchHole(
-    size: () -> Size,
-    offset: () -> Offset,
-    shape: Shape = RectangleShape,
-): Modifier = this.then(PunchHoleElement(size, offset, shape))
-
-/**
- * Punch a hole in this node using the bounds of [coords] and the given [shape].
- *
- * You can use [androidx.compose.ui.layout.onGloballyPositioned] to get the last coordinates of a
- * node.
- */
-@Stable
-fun Modifier.punchHole(
-    coords: () -> LayoutCoordinates?,
-    shape: Shape = RectangleShape,
-): Modifier = this.then(PunchHoleWithBoundsElement(coords, shape))
-
-private data class PunchHoleElement(
-    private val size: () -> Size,
-    private val offset: () -> Offset,
-    private val shape: Shape,
-) : ModifierNodeElement<PunchHoleNode>() {
-    override fun create(): PunchHoleNode = PunchHoleNode(size, offset, { shape })
-
-    override fun update(node: PunchHoleNode) {
-        node.size = size
-        node.offset = offset
-        node.shape = { shape }
-    }
-}
-
-private class PunchHoleNode(
-    var size: () -> Size,
-    var offset: () -> Offset,
-    var shape: () -> Shape,
-) : Modifier.Node(), DrawModifierNode, LayoutModifierNode {
-    private var lastSize: Size = Size.Unspecified
-    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
-    private var lastOutline: Outline? = null
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        return measurable.measure(constraints).run {
-            layout(width, height) {
-                placeWithLayer(0, 0) { compositingStrategy = CompositingStrategy.Offscreen }
-            }
-        }
-    }
-
-    override fun ContentDrawScope.draw() {
-        drawContent()
-
-        val holeSize = size()
-        if (holeSize != Size.Zero) {
-            val offset = offset()
-            translate(offset.x, offset.y) { drawHole(holeSize) }
-        }
-    }
-
-    private fun DrawScope.drawHole(size: Size) {
-        if (shape == RectangleShape) {
-            drawRect(Color.Black, size = size, blendMode = BlendMode.DstOut)
-            return
-        }
-
-        val outline =
-            if (size == lastSize && layoutDirection == lastLayoutDirection) {
-                lastOutline!!
-            } else {
-                val newOutline = shape().createOutline(size, layoutDirection, this)
-                lastSize = size
-                lastLayoutDirection = layoutDirection
-                lastOutline = newOutline
-                newOutline
-            }
-
-        drawOutline(
-            outline,
-            Color.Black,
-            blendMode = BlendMode.DstOut,
-        )
-    }
-}
-
-private data class PunchHoleWithBoundsElement(
-    private val coords: () -> LayoutCoordinates?,
-    private val shape: Shape,
-) : ModifierNodeElement<PunchHoleWithBoundsNode>() {
-    override fun create(): PunchHoleWithBoundsNode = PunchHoleWithBoundsNode(coords, shape)
-
-    override fun update(node: PunchHoleWithBoundsNode) {
-        node.holeCoords = coords
-        node.shape = shape
-    }
-}
-
-private class PunchHoleWithBoundsNode(
-    var holeCoords: () -> LayoutCoordinates?,
-    var shape: Shape,
-) : DelegatingNode(), DrawModifierNode, GlobalPositionAwareModifierNode {
-    private val delegate = delegate(PunchHoleNode(::holeSize, ::holeOffset, ::shape))
-    private var lastCoords: LayoutCoordinates? = null
-
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
-        this.lastCoords = coordinates
-    }
-
-    override fun ContentDrawScope.draw() = with(delegate) { draw() }
-
-    private fun holeSize(): Size {
-        return holeCoords()?.size?.toSize() ?: Size.Zero
-    }
-
-    private fun holeOffset(): Offset {
-        val holeCoords = holeCoords() ?: return Offset.Zero
-        val lastCoords = lastCoords ?: error("draw() was called before onGloballyPositioned()")
-        return lastCoords.localPositionOf(holeCoords, relativeToSource = Offset.Zero)
-    }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 7c8fce8..45758c5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -48,7 +48,6 @@
  * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
  *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
  * @param scenes the configuration of the different scenes of this layout.
- * @see updateSceneTransitionLayoutState
  */
 @Composable
 fun SceneTransitionLayout(
@@ -70,56 +69,6 @@
     )
 }
 
-/**
- * [SceneTransitionLayout] is a container that automatically animates its content whenever
- * [currentScene] changes, using the transitions defined in [transitions].
- *
- * Note: You should use [androidx.compose.animation.AnimatedContent] instead of
- * [SceneTransitionLayout] if it fits your need. Use [SceneTransitionLayout] over AnimatedContent if
- * you need support for swipe gestures, shared elements or transitions defined declaratively outside
- * UI code.
- *
- * @param currentScene the current scene
- * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
- *   This is called when the user commits a transition to a new scene because of a [UserAction], for
- *   instance by triggering back navigation or by swiping to a new scene.
- * @param transitions the definition of the transitions used to animate a change of scene.
- * @param swipeSourceDetector the source detector used to detect which source a swipe is started
- *   from, if any.
- * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
- *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
- * @param scenes the configuration of the different scenes of this layout.
- */
-@Composable
-fun SceneTransitionLayout(
-    currentScene: SceneKey,
-    onChangeScene: (SceneKey) -> Unit,
-    transitions: SceneTransitions,
-    modifier: Modifier = Modifier,
-    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
-    swipeDetector: SwipeDetector = DefaultSwipeDetector,
-    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
-    enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-    scenes: SceneTransitionLayoutScope.() -> Unit,
-) {
-    val state =
-        updateSceneTransitionLayoutState(
-            currentScene,
-            onChangeScene,
-            transitions,
-            enableInterruptions = enableInterruptions,
-        )
-
-    SceneTransitionLayout(
-        state,
-        modifier,
-        swipeSourceDetector,
-        swipeDetector,
-        transitionInterceptionThreshold,
-        scenes,
-    )
-}
-
 interface SceneTransitionLayoutScope {
     /**
      * Add a scene to this layout, identified by [key].
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 5b4fbf0..56c8752 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -22,13 +22,9 @@
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.spring
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastFilter
@@ -38,14 +34,12 @@
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
 
 /**
  * The state of a [SceneTransitionLayout].
  *
  * @see MutableSceneTransitionLayoutState
- * @see updateSceneTransitionLayoutState
  */
 @Stable
 sealed interface SceneTransitionLayoutState {
@@ -152,55 +146,6 @@
     )
 }
 
-/**
- * Sets up a [SceneTransitionLayoutState] and keeps it synced with [currentScene], [onChangeScene]
- * and [transitions]. New transitions will automatically be started whenever [currentScene] is
- * changed.
- *
- * @param currentScene the current scene
- * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
- *   This is called when the user commits a transition to a new scene because of a [UserAction], for
- *   instance by triggering back navigation or by swiping to a new scene.
- * @param transitions the definition of the transitions used to animate a change of scene.
- * @param canChangeScene whether we can transition to the given scene. This is called when the user
- *   commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
- *   `true`, then [onChangeScene] will be called right afterwards with the same [SceneKey]. If it
- *   returns `false`, the user action will be cancelled and we will animate back to the current
- *   scene.
- * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
- *   [SceneTransitionLayoutState]s.
- */
-@Composable
-fun updateSceneTransitionLayoutState(
-    currentScene: SceneKey,
-    onChangeScene: (SceneKey) -> Unit,
-    transitions: SceneTransitions = SceneTransitions.Empty,
-    canChangeScene: (SceneKey) -> Boolean = { true },
-    stateLinks: List<StateLink> = emptyList(),
-    enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-): SceneTransitionLayoutState {
-    return remember {
-            HoistedSceneTransitionLayoutState(
-                currentScene,
-                transitions,
-                onChangeScene,
-                canChangeScene,
-                stateLinks,
-                enableInterruptions,
-            )
-        }
-        .apply {
-            update(
-                currentScene,
-                onChangeScene,
-                canChangeScene,
-                transitions,
-                stateLinks,
-                enableInterruptions,
-            )
-        }
-}
-
 @Stable
 sealed interface TransitionState {
     /**
@@ -729,58 +674,6 @@
     }
 }
 
-/**
- * A [SceneTransitionLayout] whose current scene/source of truth is hoisted (its current value comes
- * from outside).
- */
-internal class HoistedSceneTransitionLayoutState(
-    initialScene: SceneKey,
-    override var transitions: SceneTransitions,
-    private var changeScene: (SceneKey) -> Unit,
-    private var canChangeScene: (SceneKey) -> Boolean,
-    stateLinks: List<StateLink> = emptyList(),
-    enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-) : BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) {
-    private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
-
-    override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
-
-    override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene.invoke(scene)
-
-    @Composable
-    fun update(
-        currentScene: SceneKey,
-        onChangeScene: (SceneKey) -> Unit,
-        canChangeScene: (SceneKey) -> Boolean,
-        transitions: SceneTransitions,
-        stateLinks: List<StateLink>,
-        enableInterruptions: Boolean,
-    ) {
-        SideEffect {
-            this.changeScene = onChangeScene
-            this.canChangeScene = canChangeScene
-            this.transitions = transitions
-            this.stateLinks = stateLinks
-            this.enableInterruptions = enableInterruptions
-
-            targetSceneChannel.trySend(currentScene)
-        }
-
-        LaunchedEffect(targetSceneChannel) {
-            for (newKey in targetSceneChannel) {
-                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
-                // late.
-                val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
-                animateToScene(
-                    layoutState = this@HoistedSceneTransitionLayoutState,
-                    target = newKey,
-                    transitionKey = null,
-                )
-            }
-        }
-    }
-}
-
 /** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
 internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 1ae9992..7988e0e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -203,26 +203,28 @@
         val elementSize = 50.dp
         val elementOffset = 20.dp
 
-        lateinit var changeScene: (SceneKey) -> Unit
-
-        rule.testTransition(
-            from = SceneA,
-            to = SceneB,
-            transitionLayout = { currentScene, onChangeScene ->
-                changeScene = onChangeScene
-
-                SceneTransitionLayout(
-                    currentScene,
-                    onChangeScene,
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween }
                         from(SceneB, to = SceneC) { spec = tween }
                     },
 
-                    // Disable interruptions so that the current transition is directly removed when
-                    // starting a new one.
+                    // Disable interruptions so that the current transition is directly removed
+                    // when starting a new one.
                     enableInterruptions = false,
-                ) {
+                )
+            }
+
+        lateinit var coroutineScope: CoroutineScope
+        rule.testTransition(
+            state = state,
+            to = SceneB,
+            transitionLayout = { state ->
+                coroutineScope = rememberCoroutineScope()
+                SceneTransitionLayout(state) {
                     scene(SceneA) {
                         Box(Modifier.size(layoutSize)) {
                             // Transformed element
@@ -243,7 +245,7 @@
                 onElement(TestElements.Bar).assertExists()
 
                 // Start transition from SceneB to SceneC
-                changeScene(SceneC)
+                rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
             }
 
             at(3 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() }
@@ -340,18 +342,16 @@
 
     @Test
     fun elementIsReusedBetweenScenes() {
-        var currentScene by mutableStateOf(SceneA)
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
         var sceneCState by mutableStateOf(0)
         val key = TestElements.Foo
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
 
+        lateinit var coroutineScope: CoroutineScope
         rule.setContent {
+            coroutineScope = rememberCoroutineScope()
             SceneTransitionLayoutForTesting(
-                state =
-                    updateSceneTransitionLayoutState(
-                        currentScene = currentScene,
-                        onChangeScene = { currentScene = it }
-                    ),
+                state = state,
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(SceneA) { /* Nothing */ }
@@ -375,7 +375,7 @@
         assertThat(layoutImpl.elements).isEmpty()
 
         // Scene B: element is in the map.
-        currentScene = SceneB
+        rule.runOnUiThread { state.setTargetScene(SceneB, coroutineScope) }
         rule.waitForIdle()
 
         assertThat(layoutImpl.elements.keys).containsExactly(key)
@@ -383,7 +383,7 @@
         assertThat(element.sceneStates.keys).containsExactly(SceneB)
 
         // Scene C, state 0: the same element is reused.
-        currentScene = SceneC
+        rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
         sceneCState = 0
         rule.waitForIdle()
 
@@ -472,12 +472,13 @@
 
     @Test
     fun elementModifierSupportsUpdates() {
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
         var key by mutableStateOf(TestElements.Foo)
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
 
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                state = updateSceneTransitionLayoutState(currentScene = SceneA, onChangeScene = {}),
+                state = state,
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(SceneA) { Box(Modifier.element(key)) }
@@ -521,11 +522,12 @@
             rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
         }
 
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
         rule.setContent {
             scrollScope = rememberCoroutineScope()
 
             SceneTransitionLayoutForTesting(
-                state = updateSceneTransitionLayoutState(currentScene = SceneA, onChangeScene = {}),
+                state = state,
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 5543135..f717301 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -40,7 +40,13 @@
 
     @Test
     fun testObservableTransitionState() = runTest {
-        lateinit var state: SceneTransitionLayoutState
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    EmptyTestTransitions,
+                )
+            }
 
         // Collect the current observable state into [observableState].
         // TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -63,16 +69,9 @@
         }
 
         rule.testTransition(
-            from = SceneA,
+            state = state,
             to = SceneB,
-            transitionLayout = { currentScene, onChangeScene ->
-                state =
-                    updateSceneTransitionLayoutState(
-                        currentScene,
-                        onChangeScene,
-                        EmptyTestTransitions
-                    )
-
+            transitionLayout = {
                 SceneTransitionLayout(state = state) {
                     scene(SceneA) {}
                     scene(SceneB) {}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
new file mode 100644
index 0000000..6522eb3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene
+
+import androidx.activity.BackEventCompat
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.subjects.assertThat
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PredictiveBackHandlerTest {
+    // We use createAndroidComposeRule() here and not createComposeRule() because we need an
+    // activity for testBack().
+    @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
+
+    @Test
+    fun testBack() {
+        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        rule.setContent {
+            SceneTransitionLayout(layoutState) {
+                scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+                scene(SceneB) { Box(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+        rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
+    }
+
+    @Test
+    fun testPredictiveBack() {
+        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        rule.setContent {
+            SceneTransitionLayout(layoutState) {
+                scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+                scene(SceneB) { Box(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+        // Start back.
+        val dispatcher = rule.activity.onBackPressedDispatcher
+        rule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(backEvent())
+            dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+        }
+
+        val transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasProgress(0.4f)
+
+        // Cancel it.
+        rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+
+        // Start again and commit it.
+        rule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(backEvent())
+            dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+            dispatcher.onBackPressed()
+        }
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
+        assertThat(layoutState.transitionState).isIdle()
+    }
+
+    @Test
+    fun interruptedPredictiveBackDoesNotCallCanChangeScene() {
+        var canChangeSceneCalled = false
+        val layoutState =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    canChangeScene = {
+                        canChangeSceneCalled = true
+                        true
+                    },
+                )
+            }
+
+        lateinit var coroutineScope: CoroutineScope
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            SceneTransitionLayout(layoutState) {
+                scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+                scene(SceneB) { Box(Modifier.fillMaxSize()) }
+                scene(SceneC) { Box(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+        // Start back.
+        val dispatcher = rule.activity.onBackPressedDispatcher
+        rule.runOnUiThread { dispatcher.dispatchOnBackStarted(backEvent()) }
+
+        val predictiveTransition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(predictiveTransition).hasFromScene(SceneA)
+        assertThat(predictiveTransition).hasToScene(SceneB)
+
+        // Start a new transition to C.
+        rule.runOnUiThread { layoutState.setTargetScene(SceneC, coroutineScope) }
+        val newTransition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(newTransition).hasFromScene(SceneA)
+        assertThat(newTransition).hasToScene(SceneC)
+
+        // Commit the back gesture. It shouldn't call canChangeScene given that the back transition
+        // was interrupted.
+        rule.runOnUiThread { dispatcher.onBackPressed() }
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(predictiveTransition).hasCurrentScene(SceneA)
+        assertThat(canChangeSceneCalled).isFalse()
+    }
+
+    private fun backEvent(progress: Float = 0f): BackEventCompat {
+        return BackEventCompat(
+            touchX = 0f,
+            touchY = 0f,
+            progress = progress,
+            swipeEdge = BackEventCompat.EDGE_LEFT,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 1c8efb8..1ec1079 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -16,8 +16,6 @@
 
 package com.android.compose.animation.scene
 
-import androidx.activity.BackEventCompat
-import androidx.activity.ComponentActivity
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.tween
@@ -31,6 +29,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -41,7 +41,7 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onChild
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
@@ -58,6 +58,7 @@
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
 import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
@@ -69,23 +70,27 @@
         private val LayoutSize = 300.dp
     }
 
-    private var currentScene by mutableStateOf(SceneA)
-    private lateinit var layoutState: SceneTransitionLayoutState
+    private lateinit var coroutineScope: CoroutineScope
+    private lateinit var layoutState: MutableSceneTransitionLayoutState
+    private var currentScene: SceneKey
+        get() = layoutState.transitionState.currentScene
+        set(value) {
+            rule.runOnUiThread { layoutState.setTargetScene(value, coroutineScope) }
+        }
 
-    // We use createAndroidComposeRule() here and not createComposeRule() because we need an
-    // activity for testBack().
-    @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
+    @get:Rule val rule = createComposeRule()
 
     /** The content under test. */
     @Composable
     private fun TestContent(enableInterruptions: Boolean = true) {
-        layoutState =
-            updateSceneTransitionLayoutState(
-                currentScene,
-                { currentScene = it },
+        coroutineScope = rememberCoroutineScope()
+        layoutState = remember {
+            MutableSceneTransitionLayoutState(
+                SceneA,
                 EmptyTestTransitions,
                 enableInterruptions = enableInterruptions,
             )
+        }
 
         SceneTransitionLayout(
             state = layoutState,
@@ -164,52 +169,6 @@
     }
 
     @Test
-    fun testBack() {
-        rule.setContent { TestContent() }
-
-        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-
-        rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
-        rule.waitForIdle()
-        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
-    }
-
-    @Test
-    fun testPredictiveBack() {
-        rule.setContent { TestContent() }
-
-        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-
-        // Start back.
-        val dispatcher = rule.activity.onBackPressedDispatcher
-        rule.runOnUiThread {
-            dispatcher.dispatchOnBackStarted(backEvent())
-            dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
-        }
-
-        val transition = assertThat(layoutState.transitionState).isTransition()
-        assertThat(transition).hasFromScene(SceneA)
-        assertThat(transition).hasToScene(SceneB)
-        assertThat(transition).hasProgress(0.4f)
-
-        // Cancel it.
-        rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
-        rule.waitForIdle()
-        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-        assertThat(layoutState.transitionState).isIdle()
-
-        // Start again and commit it.
-        rule.runOnUiThread {
-            dispatcher.dispatchOnBackStarted(backEvent())
-            dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
-            dispatcher.onBackPressed()
-        }
-        rule.waitForIdle()
-        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
-        assertThat(layoutState.transitionState).isIdle()
-    }
-
-    @Test
     fun testTransitionState() {
         rule.setContent { TestContent() }
         assertThat(layoutState.transitionState).isIdle()
@@ -218,23 +177,15 @@
         // We will advance the clock manually.
         rule.mainClock.autoAdvance = false
 
-        // Change the current scene. Until composition is triggered, this won't change the layout
-        // state.
+        // Change the current scene.
         currentScene = SceneB
-        assertThat(layoutState.transitionState).isIdle()
-        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-
-        // On the next frame, we will recompose because currentScene changed, which will start the
-        // transition (i.e. it will change the transitionState to be a Transition) in a
-        // LaunchedEffect.
-        rule.mainClock.advanceTimeByFrame()
         val transition = assertThat(layoutState.transitionState).isTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0f)
 
         // Then, on the next frame, the animator we started gets its initial value and clock
-        // starting time. We are now at progress = 0f.
+        // starting time. We are still at progress = 0f.
         rule.mainClock.advanceTimeByFrame()
         assertThat(transition).hasProgress(0f)
 
@@ -275,12 +226,9 @@
         // Pause animations to test the state mid-transition.
         rule.mainClock.autoAdvance = false
 
-        // Go to scene B and let the animation start. See [testLayoutState()] and
-        // [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
-        // by 2 frames to be at the start of the animation.
+        // Go to scene B and let the animation start.
         currentScene = SceneB
         rule.mainClock.advanceTimeByFrame()
-        rule.mainClock.advanceTimeByFrame()
 
         // Advance to the middle of the animation.
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -311,7 +259,6 @@
         // Animate to scene C, let the animation start then go to the middle of the transition.
         currentScene = SceneC
         rule.mainClock.advanceTimeByFrame()
-        rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
 
         // In Scene C, foo is at the bottom start of the layout and has a size of 150.dp. The
@@ -409,24 +356,24 @@
     fun multipleTransitionsWillComposeMultipleScenes() {
         val duration = 10 * 16L
 
-        var currentScene: SceneKey by mutableStateOf(SceneA)
-        lateinit var state: SceneTransitionLayoutState
-        rule.setContent {
-            state =
-                updateSceneTransitionLayoutState(
-                    currentScene = currentScene,
-                    onChangeScene = { currentScene = it },
-                    transitions =
-                        transitions {
-                            from(SceneA, to = SceneB) {
-                                spec = tween(duration.toInt(), easing = LinearEasing)
-                            }
-                            from(SceneB, to = SceneC) {
-                                spec = tween(duration.toInt(), easing = LinearEasing)
-                            }
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    transitions {
+                        from(SceneA, to = SceneB) {
+                            spec = tween(duration.toInt(), easing = LinearEasing)
                         }
+                        from(SceneB, to = SceneC) {
+                            spec = tween(duration.toInt(), easing = LinearEasing)
+                        }
+                    }
                 )
+            }
 
+        lateinit var coroutineScope: CoroutineScope
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
             SceneTransitionLayout(state) {
                 scene(SceneA) { Box(Modifier.testTag("aRoot").fillMaxSize()) }
                 scene(SceneB) { Box(Modifier.testTag("bRoot").fillMaxSize()) }
@@ -444,12 +391,11 @@
         rule.mainClock.autoAdvance = false
 
         // Start A => B and go to the middle of the transition.
-        currentScene = SceneB
+        rule.runOnUiThread { state.setTargetScene(SceneB, coroutineScope) }
 
-        // We need to tick 2 frames after changing [currentScene] before the animation actually
+        // We need to tick 1 frames after changing [currentScene] before the animation actually
         // starts.
         rule.mainClock.advanceTimeByFrame()
-        rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeBy(duration / 2)
         rule.waitForIdle()
 
@@ -462,8 +408,7 @@
         rule.onNodeWithTag("cRoot").assertDoesNotExist()
 
         // Start B => C.
-        currentScene = SceneC
-        rule.mainClock.advanceTimeByFrame()
+        rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
         rule.mainClock.advanceTimeByFrame()
         rule.waitForIdle()
 
@@ -517,12 +462,7 @@
             assertThrows(IllegalStateException::class.java) {
                 rule.setContent {
                     SceneTransitionLayout(
-                        state =
-                            updateSceneTransitionLayoutState(
-                                currentScene = currentScene,
-                                onChangeScene = { currentScene = it },
-                                transitions = EmptyTestTransitions
-                            ),
+                        state = remember { MutableSceneTransitionLayoutState(SceneA) },
                         modifier = Modifier.size(LayoutSize),
                     ) {
                         // from SceneA to SceneA
@@ -560,13 +500,4 @@
         assertThat(keyInB).isEqualTo(SceneB)
         assertThat(keyInC).isEqualTo(SceneC)
     }
-
-    private fun backEvent(progress: Float = 0f): BackEventCompat {
-        return BackEventCompat(
-            touchX = 0f,
-            touchY = 0f,
-            progress = progress,
-            swipeEdge = BackEventCompat.EDGE_LEFT,
-        )
-    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
index de46f72..fbd557f 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
@@ -27,12 +27,6 @@
     content: @Composable SceneScope.() -> Unit,
 ) {
     val currentScene = remember { SceneKey("current") }
-    SceneTransitionLayout(
-        currentScene,
-        onChangeScene = { /* do nothing */},
-        transitions = remember { transitions {} },
-        modifier,
-    ) {
-        scene(currentScene, content = content)
-    }
+    val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+    SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 6724851..a37d78e 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -19,13 +19,14 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import kotlinx.coroutines.CoroutineScope
 import platform.test.motion.MotionTestRule
 import platform.test.motion.RecordedMotion
 import platform.test.motion.compose.ComposeRecordingSpec
@@ -95,20 +96,24 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        from = fromScene,
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    fromScene,
+                    transitions { from(fromScene, to = toScene, builder = transition) }
+                )
+            },
         to = toScene,
-        transitionLayout = { currentScene, onChangeScene ->
+        transitionLayout = { state ->
             SceneTransitionLayout(
-                currentScene,
-                onChangeScene,
-                transitions { from(fromScene, to = toScene, builder = transition) },
+                state,
                 layoutModifier,
             ) {
                 scene(fromScene, content = fromSceneContent)
                 scene(toScene, content = toSceneContent)
             }
         },
-        builder,
+        builder = builder,
     )
 }
 
@@ -172,21 +177,19 @@
     )
 }
 
-/**
- * Test the transition between two scenes of [transitionLayout][SceneTransitionLayout] at different
- * points in time.
- */
+/** Test the transition from [state] to [to]. */
 fun ComposeContentTestRule.testTransition(
-    from: SceneKey,
+    state: MutableSceneTransitionLayoutState,
     to: SceneKey,
-    transitionLayout:
-        @Composable
-        (
-            currentScene: SceneKey,
-            onChangeScene: (SceneKey) -> Unit,
-        ) -> Unit,
+    transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
     builder: TransitionTestBuilder.() -> Unit,
 ) {
+    val currentScene = state.transitionState.currentScene
+    check(currentScene != to) {
+        "The 'to' scene (${to.debugName}) should be different from the state current scene " +
+            "(${currentScene.debugName})"
+    }
+
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
@@ -198,8 +201,11 @@
             }
         }
 
-    var currentScene by mutableStateOf(from)
-    setContent { transitionLayout(currentScene, { currentScene = it }) }
+    lateinit var coroutineScope: CoroutineScope
+    setContent {
+        coroutineScope = rememberCoroutineScope()
+        transitionLayout(state)
+    }
 
     // Wait for the UI to be idle then test the before state.
     waitForIdle()
@@ -209,14 +215,8 @@
     mainClock.autoAdvance = false
 
     // Change the current scene.
-    currentScene = to
-
-    // Advance by a frame to trigger recomposition, which will start the transition (i.e. it will
-    // change the transitionState to be a Transition) in a LaunchedEffect.
-    mainClock.advanceTimeByFrame()
-
-    // Advance by another frame so that the animator we started gets its initial value and clock
-    // starting time. We are now at progress = 0f.
+    runOnUiThread { state.setTargetScene(to, coroutineScope) }
+    waitForIdle()
     mainClock.advanceTimeByFrame()
     waitForIdle()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
new file mode 100644
index 0000000..2f94933
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.communal
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnableFlags(FLAG_COMMUNAL_HUB)
+@RunWith(AndroidJUnit4::class)
+class CommunalOngoingContentStartableTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val mediaRepository = kosmos.fakeCommunalMediaRepository
+    private val smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
+    private val featureFlags =
+        kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+
+    private lateinit var underTest: CommunalOngoingContentStartable
+
+    @Before
+    fun setUp() {
+        underTest =
+            CommunalOngoingContentStartable(
+                bgScope = kosmos.applicationCoroutineScope,
+                communalInteractor = kosmos.communalInteractor,
+                communalMediaRepository = mediaRepository,
+                communalSmartspaceRepository = smartspaceRepository,
+                featureFlags = featureFlags,
+            )
+    }
+
+    @Test
+    fun testListenForOngoingContentWhenCommunalIsEnabled() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            assertThat(mediaRepository.isListening()).isFalse()
+            assertThat(smartspaceRepository.isListening()).isFalse()
+
+            kosmos.setCommunalEnabled(true)
+            runCurrent()
+
+            assertThat(mediaRepository.isListening()).isTrue()
+            assertThat(smartspaceRepository.isListening()).isTrue()
+
+            kosmos.setCommunalEnabled(false)
+            runCurrent()
+
+            assertThat(mediaRepository.isListening()).isFalse()
+            assertThat(smartspaceRepository.isListening()).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 407bf4c..dd28022 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,46 +20,41 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 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.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @android.platform.test.annotations.EnabledOnRavenwood
 class CommunalMediaRepositoryImplTest : SysuiTestCase() {
-    @Mock private lateinit var mediaDataManager: MediaDataManager
-    @Mock private lateinit var mediaData: MediaData
-    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+    private val mediaDataManager = mock<MediaDataManager>()
+    private val mediaData = mock<MediaData>()
+    private val tableLogBuffer = mock<TableLogBuffer>()
 
     private lateinit var underTest: CommunalMediaRepositoryImpl
 
-    private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
-        KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
-    }
+    private val mediaDataListenerCaptor = argumentCaptor<MediaDataManager.Listener>()
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
         underTest =
             CommunalMediaRepositoryImpl(
                 mediaDataManager,
@@ -78,6 +73,8 @@
     @Test
     fun mediaModel_updatesWhenMediaDataLoaded() =
         testScope.runTest {
+            underTest.startListening()
+
             // Listener is added
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
 
@@ -89,7 +86,7 @@
             // Change to media available and notify the listener.
             whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
-            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+            mediaDataListenerCaptor.firstValue.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
@@ -100,12 +97,14 @@
     @Test
     fun mediaModel_updatesWhenMediaDataRemoved() =
         testScope.runTest {
+            underTest.startListening()
+
             // Listener is added
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
 
             // Change to media available and notify the listener.
             whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
-            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+            mediaDataListenerCaptor.firstValue.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
@@ -114,7 +113,7 @@
 
             // Change to media unavailable and notify the listener.
             whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
-            mediaDataListenerCaptor.value.onMediaDataRemoved("key", false)
+            mediaDataListenerCaptor.firstValue.onMediaDataRemoved("key", false)
             runCurrent()
 
             // Media active now returns false.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
new file mode 100644
index 0000000..c1816ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2024 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.communal.data.repository
+
+import android.app.smartspace.SmartspaceTarget
+import android.app.smartspace.flags.Flags.FLAG_REMOTE_VIEWS
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalSmartspaceRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val listenerCaptor = argumentCaptor<SmartspaceTargetListener>()
+
+    private val smartspaceController = mock<CommunalSmartspaceController>()
+    private val fakeExecutor = kosmos.fakeExecutor
+    private val systemClock = kosmos.fakeSystemClock
+
+    private lateinit var underTest: CommunalSmartspaceRepositoryImpl
+
+    @Before
+    fun setUp() {
+        underTest =
+            CommunalSmartspaceRepositoryImpl(
+                smartspaceController,
+                fakeExecutor,
+                systemClock,
+            )
+    }
+
+    @DisableFlags(FLAG_REMOTE_VIEWS)
+    @Test
+    fun startListening_remoteViewsFlagDisabled_doNotListenForSmartspaceUpdates() =
+        testScope.runTest {
+            underTest.startListening()
+            fakeExecutor.runAllReady()
+
+            verify(smartspaceController, never()).addListener(any())
+        }
+
+    @EnableFlags(FLAG_REMOTE_VIEWS)
+    @Test
+    fun startListening_remoteViewsFlagEnabled_listenForSmartspaceUpdates() =
+        testScope.runTest {
+            underTest.startListening()
+            fakeExecutor.runAllReady()
+
+            // Verify listener added
+            val listener = captureSmartspaceTargetListener()
+
+            underTest.stopListening()
+            fakeExecutor.runAllReady()
+
+            // Verify listener removed
+            verify(smartspaceController).removeListener(listener)
+        }
+
+    @EnableFlags(FLAG_REMOTE_VIEWS)
+    @Test
+    fun communalTimers_onlyShowTimersWithRemoteViews() =
+        testScope.runTest {
+            underTest.startListening()
+
+            val communalTimers by collectLastValue(underTest.timers)
+            runCurrent()
+            fakeExecutor.runAllReady()
+
+            with(captureSmartspaceTargetListener()) {
+                onSmartspaceTargetsUpdated(
+                    listOf(
+                        // Invalid. Not a timer
+                        mock<SmartspaceTarget> {
+                            on { smartspaceTargetId }.doReturn("weather")
+                            on { featureType }.doReturn(SmartspaceTarget.FEATURE_WEATHER)
+                        },
+                        // Invalid. RemoteViews absent
+                        mock<SmartspaceTarget> {
+                            on { smartspaceTargetId }.doReturn("timer-0-started")
+                            on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                            on { remoteViews }.doReturn(null)
+                            on { creationTimeMillis }.doReturn(1000)
+                        },
+                        // Valid
+                        mock<SmartspaceTarget> {
+                            on { smartspaceTargetId }.doReturn("timer-1-started")
+                            on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                            on { remoteViews }.doReturn(mock())
+                            on { creationTimeMillis }.doReturn(2000)
+                        },
+                    )
+                )
+            }
+            runCurrent()
+
+            // Verify that only the valid target is listed
+            assertThat(communalTimers).hasSize(1)
+            assertThat(communalTimers?.first()?.smartspaceTargetId).isEqualTo("timer-1-started")
+        }
+
+    @EnableFlags(FLAG_REMOTE_VIEWS)
+    @Test
+    fun communalTimers_cacheCreationTime() =
+        testScope.runTest {
+            underTest.startListening()
+
+            val communalTimers by collectLastValue(underTest.timers)
+            runCurrent()
+            fakeExecutor.runAllReady()
+
+            val listener = captureSmartspaceTargetListener()
+            listener.onSmartspaceTargetsUpdated(
+                listOf(
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-0-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(1000)
+                    },
+                )
+            )
+            runCurrent()
+
+            // Verify that the creation time is the current time, not the creation time passed in
+            // the target, because this value can be inaccurate (due to b/318535930).
+            val currentTime = systemClock.currentTimeMillis()
+            assertThat(communalTimers?.get(0)?.createdTimestampMillis).isEqualTo(currentTime)
+            assertThat(communalTimers?.get(0)?.createdTimestampMillis).isNotEqualTo(1000)
+
+            // A second timer is added.
+            listener.onSmartspaceTargetsUpdated(
+                listOf(
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-0-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(2000)
+                    },
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-1-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(3000)
+                    },
+                )
+            )
+            runCurrent()
+
+            // Verify that the created timestamp for the first time is consistent
+            assertThat(communalTimers?.get(0)?.createdTimestampMillis).isEqualTo(currentTime)
+
+            // Verify that the second timer has a new creation time
+            assertThat(communalTimers?.get(1)?.createdTimestampMillis)
+                .isEqualTo(systemClock.currentTimeMillis())
+        }
+
+    @EnableFlags(FLAG_REMOTE_VIEWS)
+    @Test
+    fun communalTimers_creationTimeRemovedFromCacheWhenTimerRemoved() =
+        testScope.runTest {
+            underTest.startListening()
+
+            val communalTimers by collectLastValue(underTest.timers)
+            runCurrent()
+            fakeExecutor.runAllReady()
+
+            // Start timer 0
+            val listener = captureSmartspaceTargetListener()
+            listener.onSmartspaceTargetsUpdated(
+                listOf(
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-0-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(1000)
+                    },
+                )
+            )
+            runCurrent()
+
+            // Verify timer 0 creation time
+            val expectedCreationTimeForTimer0 = systemClock.currentTimeMillis()
+            assertThat(communalTimers?.first()?.createdTimestampMillis)
+                .isEqualTo(expectedCreationTimeForTimer0)
+
+            // Advance some time
+            systemClock.advanceTime(1000)
+
+            // Start timer 1
+            listener.onSmartspaceTargetsUpdated(
+                listOf(
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-0-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(1000)
+                    },
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-1-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(2000)
+                    },
+                )
+            )
+            runCurrent()
+
+            // Verify timer 1 creation time is new
+            val expectedCreationTimeForTimer1 = expectedCreationTimeForTimer0 + 1000
+            assertThat(communalTimers?.get(1)?.createdTimestampMillis)
+                .isEqualTo(expectedCreationTimeForTimer1)
+
+            // Removed timer 0
+            listener.onSmartspaceTargetsUpdated(
+                listOf(
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-1-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(2000)
+                    },
+                )
+            )
+            runCurrent()
+
+            // Verify timer 0 removed, and timer 1 creation time is correct
+            assertThat(communalTimers).hasSize(1)
+            assertThat(communalTimers?.first()?.createdTimestampMillis)
+                .isEqualTo(expectedCreationTimeForTimer1)
+
+            // Advance some time
+            systemClock.advanceTime(1000)
+
+            // Start timer 0 again. Technically this is a new timer, but timers can reused stable
+            // ids.
+            listener.onSmartspaceTargetsUpdated(
+                listOf(
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-1-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(2000)
+                    },
+                    mock<SmartspaceTarget> {
+                        on { smartspaceTargetId }.doReturn("timer-0-started")
+                        on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+                        on { remoteViews }.doReturn(mock())
+                        on { creationTimeMillis }.doReturn(3000)
+                    },
+                )
+            )
+            runCurrent()
+
+            // Verify new timer added, and timer 1 creation time is still correct
+            assertThat(communalTimers).hasSize(2)
+            assertThat(communalTimers?.get(0)?.createdTimestampMillis)
+                .isEqualTo(expectedCreationTimeForTimer1)
+
+            // Verify creation time for the new timer is new, meaning that cache for timer 0 was
+            // removed when it was removed
+            assertThat(communalTimers?.get(1)?.createdTimestampMillis)
+                .isEqualTo(expectedCreationTimeForTimer1 + 1000)
+        }
+
+    @Test
+    fun stableId() {
+        assertThat(CommunalSmartspaceRepositoryImpl.stableId("timer-0-12345-started"))
+            .isEqualTo("timer-0")
+        assertThat(CommunalSmartspaceRepositoryImpl.stableId("timer-1-67890-paused"))
+            .isEqualTo("timer-1")
+        assertThat(CommunalSmartspaceRepositoryImpl.stableId("i_am_an_unexpected_id"))
+            .isEqualTo("i_am_an_unexpected_id")
+    }
+
+    private fun captureSmartspaceTargetListener(): SmartspaceTargetListener {
+        verify(smartspaceController).addListener(listenerCaptor.capture())
+        return listenerCaptor.firstValue
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 7b26db5..5cdbe9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,7 +19,6 @@
 
 import android.app.admin.DevicePolicyManager
 import android.app.admin.devicePolicyManager
-import android.app.smartspace.SmartspaceTarget
 import android.appwidget.AppWidgetProviderInfo
 import android.content.Intent
 import android.content.pm.UserInfo
@@ -36,14 +35,17 @@
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -69,8 +71,6 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -114,7 +114,7 @@
     private lateinit var communalRepository: FakeCommunalSceneRepository
     private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
     private lateinit var userRepository: FakeUserRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
@@ -135,7 +135,7 @@
         communalRepository = kosmos.fakeCommunalSceneRepository
         mediaRepository = kosmos.fakeCommunalMediaRepository
         widgetRepository = kosmos.fakeCommunalWidgetRepository
-        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
         userRepository = kosmos.fakeUserRepository
         keyguardRepository = kosmos.fakeKeyguardRepository
         editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
@@ -265,44 +265,6 @@
         }
 
     @Test
-    fun smartspace_onlyShowTimersWithRemoteViews() =
-        testScope.runTest {
-            // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            // Not a timer
-            val target1 = mock(SmartspaceTarget::class.java)
-            whenever(target1.smartspaceTargetId).thenReturn("target1")
-            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
-            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
-            whenever(target1.creationTimeMillis).thenReturn(0L)
-
-            // Does not have RemoteViews
-            val target2 = mock(SmartspaceTarget::class.java)
-            whenever(target2.smartspaceTargetId).thenReturn("target2")
-            whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target2.remoteViews).thenReturn(null)
-            whenever(target2.creationTimeMillis).thenReturn(0L)
-
-            // Timer and has RemoteViews
-            val target3 = mock(SmartspaceTarget::class.java)
-            whenever(target3.smartspaceTargetId).thenReturn("target3")
-            whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
-            whenever(target3.creationTimeMillis).thenReturn(0L)
-
-            val targets = listOf(target1, target2, target3)
-            smartspaceRepository.setCommunalSmartspaceTargets(targets)
-
-            val smartspaceContent by collectLastValue(underTest.getOngoingContent(true))
-            assertThat(smartspaceContent?.size).isEqualTo(1)
-            assertThat(smartspaceContent?.get(0)?.key)
-                .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
-        }
-
-    @Test
     fun smartspaceDynamicSizing_oneCard_fullSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 1,
@@ -387,12 +349,12 @@
             keyguardRepository.setKeyguardOccluded(false)
             tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
-            val targets = mutableListOf<SmartspaceTarget>()
+            val targets = mutableListOf<CommunalSmartspaceTimer>()
             for (index in 0 until totalTargets) {
                 targets.add(smartspaceTimer(index.toString()))
             }
 
-            smartspaceRepository.setCommunalSmartspaceTargets(targets)
+            smartspaceRepository.setTimers(targets)
 
             val smartspaceContent by collectLastValue(underTest.getOngoingContent(true))
             assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
@@ -441,18 +403,18 @@
 
             // Timer1 started
             val timer1 = smartspaceTimer("timer1", timestamp = 1L)
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1))
+            smartspaceRepository.setTimers(listOf(timer1))
 
             // Umo started
             mediaRepository.mediaActive(timestamp = 2L)
 
             // Timer2 started
             val timer2 = smartspaceTimer("timer2", timestamp = 3L)
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2))
+            smartspaceRepository.setTimers(listOf(timer1, timer2))
 
             // Timer3 started
             val timer3 = smartspaceTimer("timer3", timestamp = 4L)
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3))
+            smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
 
             val ongoingContent by collectLastValue(underTest.getOngoingContent(true))
             assertThat(ongoingContent?.size).isEqualTo(4)
@@ -1089,13 +1051,12 @@
             assertThat(showCommunalFromOccluded).isTrue()
         }
 
-    private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
-        val timer = mock(SmartspaceTarget::class.java)
-        whenever(timer.smartspaceTargetId).thenReturn(id)
-        whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-        whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java))
-        whenever(timer.creationTimeMillis).thenReturn(timestamp)
-        return timer
+    private fun smartspaceTimer(id: String, timestamp: Long = 0L): CommunalSmartspaceTimer {
+        return CommunalSmartspaceTimer(
+            smartspaceTargetId = id,
+            createdTimestampMillis = timestamp,
+            remoteViews = mock(RemoteViews::class.java)
+        )
     }
 
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 0190ccb..a2f6796 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.communal.view.viewmodel
 
-import android.app.smartspace.SmartspaceTarget
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ActivityNotFoundException
 import android.content.Intent
@@ -32,10 +31,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -57,8 +59,6 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.settings.fakeUserTracker
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
@@ -91,7 +91,7 @@
 
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
     private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var communalSceneInteractor: CommunalSceneInteractor
 
@@ -105,7 +105,7 @@
 
         tutorialRepository = kosmos.fakeCommunalTutorialRepository
         widgetRepository = kosmos.fakeCommunalWidgetRepository
-        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
         mediaRepository = kosmos.fakeCommunalMediaRepository
         communalSceneInteractor = kosmos.communalSceneInteractor
         kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
@@ -152,11 +152,15 @@
             widgetRepository.setCommunalWidgets(widgets)
 
             // Smartspace available.
-            val target = Mockito.mock(SmartspaceTarget::class.java)
-            whenever(target.smartspaceTargetId).thenReturn("target")
-            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+            smartspaceRepository.setTimers(
+                listOf(
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    )
+                )
+            )
 
             // Media playing.
             mediaRepository.mediaActive()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index d338774..74a048d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.communal.view.viewmodel
 
-import android.app.smartspace.SmartspaceTarget
 import android.appwidget.AppWidgetProviderInfo
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -27,12 +26,15 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -78,8 +80,6 @@
 import com.android.systemui.shade.ShadeTestUtil
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -115,7 +115,7 @@
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
     private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var userRepository: FakeUserRepository
     private lateinit var shadeTestUtil: ShadeTestUtil
@@ -136,7 +136,7 @@
         keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
         tutorialRepository = kosmos.fakeCommunalTutorialRepository
         widgetRepository = kosmos.fakeCommunalWidgetRepository
-        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
         mediaRepository = kosmos.fakeCommunalMediaRepository
         userRepository = kosmos.fakeUserRepository
         shadeTestUtil = kosmos.shadeTestUtil
@@ -222,11 +222,15 @@
             widgetRepository.setCommunalWidgets(widgets)
 
             // Smartspace available.
-            val target = Mockito.mock(SmartspaceTarget::class.java)
-            whenever(target.smartspaceTargetId).thenReturn("target")
-            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+            smartspaceRepository.setTimers(
+                listOf(
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    )
+                )
+            )
 
             // Media playing.
             mediaRepository.mediaActive()
@@ -293,7 +297,7 @@
             widgetRepository.setCommunalWidgets(emptyList())
             // UMO playing
             mediaRepository.mediaActive()
-            smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+            smartspaceRepository.setTimers(emptyList())
 
             val isEmptyState by collectLastValue(underTest.isEmptyState)
             assertThat(isEmptyState).isTrue()
@@ -314,7 +318,7 @@
                 ),
             )
             mediaRepository.mediaInactive()
-            smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+            smartspaceRepository.setTimers(emptyList())
 
             val isEmptyState by collectLastValue(underTest.isEmptyState)
             assertThat(isEmptyState).isFalse()
@@ -689,11 +693,15 @@
             advanceTimeBy(60L)
 
             // New timer available
-            val target = Mockito.mock(SmartspaceTarget::class.java)
-            whenever<String?>(target.smartspaceTargetId).thenReturn("target")
-            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+            smartspaceRepository.setTimers(
+                listOf(
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    )
+                )
+            )
             runCurrent()
 
             // Still only emits widgets and the CTA tile
@@ -748,11 +756,15 @@
             assertThat(communalContent).hasSize(3)
 
             // When new timer available
-            val target = Mockito.mock(SmartspaceTarget::class.java)
-            whenever(target.smartspaceTargetId).thenReturn("target")
-            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+            smartspaceRepository.setTimers(
+                listOf(
+                    CommunalSmartspaceTimer(
+                        smartspaceTargetId = "target",
+                        createdTimestampMillis = 0L,
+                        remoteViews = Mockito.mock(RemoteViews::class.java),
+                    )
+                )
+            )
             runCurrent()
 
             // Then emits timer, widgets and the CTA tile
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5a39de8..444f63a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -34,6 +34,9 @@
 import androidx.lifecycle.LifecycleRegistry
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.ViewCaptureFactory
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -79,6 +82,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -114,11 +118,11 @@
 
     @Mock
     lateinit var mDreamComplicationComponentFactory:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
 
     @Mock
     lateinit var mDreamComplicationComponent:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent
 
     @Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
 
@@ -154,8 +158,12 @@
 
     @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController
 
+    @Mock lateinit var mLazyViewCapture: Lazy<ViewCapture>
+
+    private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var communalRepository: FakeCommunalSceneRepository
+    private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
 
     @Captor var mViewCaptor: ArgumentCaptor<View>? = null
     private lateinit var mService: DreamOverlayService
@@ -192,13 +200,16 @@
         whenever(mDreamOverlayContainerViewController.containerView)
             .thenReturn(mDreamOverlayContainerView)
         whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
+        whenever(mLazyViewCapture.value).thenReturn(viewCaptureSpy)
         mWindowParams = WindowManager.LayoutParams()
+        mViewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(mWindowManager,
+                mLazyViewCapture, isViewCaptureEnabled = false)
         mService =
             DreamOverlayService(
                 mContext,
                 mLifecycleOwner,
                 mMainExecutor,
-                mWindowManager,
+                mViewCaptureAwareWindowManager,
                 mComplicationComponentFactory,
                 mDreamComplicationComponentFactory,
                 mDreamOverlayComponentFactory,
@@ -246,7 +257,7 @@
         mMainExecutor.runAllReady()
         verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START)
         verify(mUiEventLogger)
-            .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
+                .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 74eee9b..693fcda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.haptics.qs
 
 import android.os.VibrationEffect
+import android.service.quicksettings.Tile
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.falsingManager
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.qsTileFactory
@@ -69,6 +71,7 @@
             QSLongPressEffect(
                 vibratorHelper,
                 kosmos.keyguardStateController,
+                kosmos.falsingManager,
             )
         longPressEffect.callback = callback
         longPressEffect.qsTile = qsTile
@@ -175,17 +178,17 @@
         }
 
     @Test
-    fun onAnimationComplete_keyguardDismissible_effectCompletes() =
+    fun onAnimationComplete_keyguardDismissible_effectEndsInLongClicked() =
         testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
             // GIVEN that the animation completes
             longPressEffect.handleAnimationComplete()
 
-            // THEN the long-press effect completes
-            assertEffectCompleted()
+            // THEN the long-press effect completes with a long-click state
+            assertEffectCompleted(QSLongPressEffect.State.LONG_CLICKED)
         }
 
     @Test
-    fun onAnimationComplete_keyguardNotDismissible_effectEndsWithReset() =
+    fun onAnimationComplete_keyguardNotDismissible_effectEndsInIdleWithReset() =
         testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
             // GIVEN that the keyguard is not dismissible
             whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
@@ -193,19 +196,20 @@
             // GIVEN that the animation completes
             longPressEffect.handleAnimationComplete()
 
-            // THEN the long-press effect completes and the properties are called to reset
-            assertEffectCompleted()
+            // THEN the long-press effect ends in the idle state and the properties are reset
+            assertEffectCompleted(QSLongPressEffect.State.IDLE)
             verify(callback, times(1)).onResetProperties()
         }
 
     @Test
-    fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversing() =
+    fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
         testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
             // GIVEN that the animation completes
             longPressEffect.handleAnimationComplete()
 
-            // THEN the callback for finished reversing is used.
+            // THEN the callback for finished reversing is used and the effect ends with a click.
             verify(callback, times(1)).onEffectFinishedReversing()
+            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.CLICKED)
         }
 
     @Test
@@ -262,18 +266,88 @@
         }
 
     @Test
-    fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
-        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
-            // GIVEN that no QSTile has been set
-            longPressEffect.qsTile = null
-
+    fun onTileClick_whileIdle_withQSTile_clicks() =
+        testWhileInState(QSLongPressEffect.State.IDLE) {
             // GIVEN that a click was detected
             val couldClick = longPressEffect.onTileClick()
 
+            // THEN the click is successful
+            assertThat(couldClick).isTrue()
+        }
+
+    @Test
+    fun onTileClick_whenBouncerIsShowing_ignoresClick() =
+        testWhileInState(QSLongPressEffect.State.IDLE) {
+            // GIVEN that the bouncer is showing
+            whenever(kosmos.keyguardStateController.isPrimaryBouncerShowing).thenReturn(true)
+
+            // WHEN a click is detected by the tile view
+            val couldClick = longPressEffect.onTileClick()
+
             // THEN the click is not successful
             assertThat(couldClick).isFalse()
         }
 
+    @Test
+    fun getStateForClick_withUnavailableTile_returnsIdle() {
+        // GIVEN an unavailable tile
+        qsTile.state?.state = Tile.STATE_UNAVAILABLE
+
+        // WHEN determining the state of a click action
+        val clickState = longPressEffect.getStateForClick()
+
+        // THEN the state is IDLE
+        assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+    }
+
+    @Test
+    fun getStateForClick_withFalseTapWhenLocked_returnsIdle() {
+        // GIVEN an active tile
+        qsTile.state?.state = Tile.STATE_ACTIVE
+
+        // GIVEN that the device is locked and a false tap is detected
+        whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+        kosmos.falsingManager.setFalseTap(true)
+
+        // WHEN determining the state of a click action
+        val clickState = longPressEffect.getStateForClick()
+
+        // THEN the state is IDLE
+        assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+    }
+
+    @Test
+    fun getStateForClick_withValidTapAndTile_returnsClicked() {
+        // GIVEN an active tile
+        qsTile.state?.state = Tile.STATE_ACTIVE
+
+        // GIVEN that the device is locked and a false tap is not detected
+        whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+        kosmos.falsingManager.setFalseTap(false)
+
+        // WHEN determining the state of a click action
+        val clickState = longPressEffect.getStateForClick()
+
+        // THEN the state is CLICKED
+        assertThat(clickState).isEqualTo(QSLongPressEffect.State.CLICKED)
+    }
+
+    @Test
+    fun getStateForClick_withNullTile_returnsIdle() {
+        // GIVEN that the tile is null
+        longPressEffect.qsTile = null
+
+        // GIVEN that the device is locked and a false tap is not detected
+        whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+        kosmos.falsingManager.setFalseTap(false)
+
+        // WHEN determining the state of a click action
+        val clickState = longPressEffect.getStateForClick()
+
+        // THEN the state is IDLE
+        assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+    }
+
     private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
         with(kosmos) {
             testScope.runTest {
@@ -339,14 +413,14 @@
     /**
      * Asserts that the effect completes by checking that:
      * 1. The final snap haptics are played
-     * 2. The internal state goes back to [QSLongPressEffect.State.IDLE]
+     * 2. The internal state goes back to specified end state.
      */
-    private fun assertEffectCompleted() {
+    private fun assertEffectCompleted(endState: QSLongPressEffect.State) {
         val snapEffect = LongPressHapticBuilder.createSnapEffect()
 
         assertThat(snapEffect).isNotNull()
         assertThat(vibratorHelper.hasVibratedWithEffects(snapEffect!!)).isTrue()
-        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+        assertThat(longPressEffect.state).isEqualTo(endState)
     }
 
     /**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index 26b56a1..4862104 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -81,10 +80,10 @@
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
             kosmos.keyguardInteractor.setClockShouldBeCentered(true)
-            assertThat(value).isEqualTo(true)
+            assertThat(value).isTrue()
 
             kosmos.keyguardInteractor.setClockShouldBeCentered(false)
-            assertThat(value).isEqualTo(false)
+            assertThat(value).isFalse()
         }
 
     @Test
@@ -103,7 +102,7 @@
     fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockSize)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
             kosmos.activeNotificationListRepository.setActiveNotifs(1)
             assertThat(value).isEqualTo(ClockSize.SMALL)
         }
@@ -113,7 +112,7 @@
     fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockSize)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
             val userMedia = MediaData().copy(active = true)
             kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
             assertThat(value).isEqualTo(ClockSize.SMALL)
@@ -125,7 +124,7 @@
         testScope.runTest {
             val value by collectLastValue(underTest.clockSize)
             val userMedia = MediaData().copy(active = true)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
             kosmos.keyguardRepository.setIsDozing(false)
             assertThat(value).isEqualTo(ClockSize.SMALL)
@@ -136,7 +135,7 @@
     fun clockSize_SceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockSize)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.keyguardRepository.setIsDozing(false)
             assertThat(value).isEqualTo(ClockSize.LARGE)
         }
@@ -147,7 +146,7 @@
         testScope.runTest {
             val value by collectLastValue(underTest.clockSize)
             val userMedia = MediaData().copy(active = true)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
             kosmos.keyguardRepository.setIsDozing(true)
             assertThat(value).isEqualTo(ClockSize.LARGE)
@@ -158,8 +157,8 @@
     fun clockShouldBeCentered_sceneContainerFlagOn_notSplitMode_true() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
-            assertThat(value).isEqualTo(true)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+            assertThat(value).isTrue()
         }
 
     @Test
@@ -167,9 +166,9 @@
     fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_noActiveNotifications_true() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.activeNotificationListRepository.setActiveNotifs(0)
-            assertThat(value).isEqualTo(true)
+            assertThat(value).isTrue()
         }
 
     @Test
@@ -177,10 +176,10 @@
     fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.activeNotificationListRepository.setActiveNotifs(1)
             kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            assertThat(value).isEqualTo(true)
+            assertThat(value).isTrue()
         }
 
     @Test
@@ -188,11 +187,11 @@
     fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.activeNotificationListRepository.setActiveNotifs(1)
             kosmos.headsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
             kosmos.keyguardRepository.setIsDozing(true)
-            assertThat(value).isEqualTo(false)
+            assertThat(value).isFalse()
         }
 
     @Test
@@ -200,10 +199,10 @@
     fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_onAod_true() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.activeNotificationListRepository.setActiveNotifs(1)
             transitionTo(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
-            assertThat(value).isEqualTo(true)
+            assertThat(value).isTrue()
         }
 
     @Test
@@ -211,10 +210,10 @@
     fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_offAod_false() =
         testScope.runTest {
             val value by collectLastValue(underTest.clockShouldBeCentered)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             kosmos.activeNotificationListRepository.setActiveNotifs(1)
             transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
-            assertThat(value).isEqualTo(false)
+            assertThat(value).isFalse()
         }
 
     private suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 875e9e0..50772ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
 import com.android.systemui.util.mockito.whenever
@@ -76,7 +75,7 @@
     fun setup() {
         with(kosmos) {
             fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
-            shadeRepository.setShadeMode(ShadeMode.Single)
+            shadeRepository.setShadeLayoutWide(false)
             underTest = lockscreenContentViewModel
         }
     }
@@ -126,7 +125,7 @@
         with(kosmos) {
             testScope.runTest {
                 val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
 
                 assertThat(areNotificationsVisible).isTrue()
@@ -156,24 +155,24 @@
         }
 
     @Test
-    fun shouldUseSplitNotificationShade_withConfigTrue_true() =
+    fun isShadeLayoutWide_withConfigTrue_true() =
         with(kosmos) {
             testScope.runTest {
-                val shouldUseSplitNotificationShade by
-                    collectLastValue(underTest.shouldUseSplitNotificationShade)
-                shadeRepository.setShadeMode(ShadeMode.Split)
-                assertThat(shouldUseSplitNotificationShade).isTrue()
+                val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+                shadeRepository.setShadeLayoutWide(true)
+
+                assertThat(isShadeLayoutWide).isTrue()
             }
         }
 
     @Test
-    fun shouldUseSplitNotificationShade_withConfigFalse_false() =
+    fun isShadeLayoutWide_withConfigFalse_false() =
         with(kosmos) {
             testScope.runTest {
-                val shouldUseSplitNotificationShade by
-                    collectLastValue(underTest.shouldUseSplitNotificationShade)
-                shadeRepository.setShadeMode(ShadeMode.Single)
-                assertThat(shouldUseSplitNotificationShade).isFalse()
+                val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+                shadeRepository.setShadeLayoutWide(false)
+
+                assertThat(isShadeLayoutWide).isFalse()
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 4eb146d..3db9ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.scene.shared.model.TransitionKeys
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
@@ -182,13 +181,7 @@
                 }
             )
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-            kosmos.shadeRepository.setShadeMode(
-                if (isSingleShade) {
-                    ShadeMode.Single
-                } else {
-                    ShadeMode.Split
-                }
-            )
+            kosmos.shadeRepository.setShadeLayoutWide(!isSingleShade)
             kosmos.setCommunalAvailable(isCommunalAvailable)
             kosmos.fakePowerRepository.updateWakefulness(
                 rawState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt
new file mode 100644
index 0000000..188f2ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 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.qs
+
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.settings.SettingsProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SettingObserverTest : SysuiTestCase() {
+
+    private val DEFAULT_VALUE = 7
+
+    @Mock lateinit var settingsProxy: SettingsProxy
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+    private lateinit var testSettingObserver: SettingObserver
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(settingsProxy.getInt(any(), any())).thenReturn(5)
+        whenever(settingsProxy.getUriFor(any())).thenReturn(Uri.parse("content://test_uri"))
+        testSettingObserver =
+            object :
+                SettingObserver(
+                    settingsProxy,
+                    Handler(Looper.getMainLooper()),
+                    "test_setting",
+                    DEFAULT_VALUE
+                ) {
+                override fun handleValueChanged(value: Int, observedChange: Boolean) {}
+            }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_true_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        verify(settingsProxy)
+            .registerContentObserverAsync(
+                any<Uri>(),
+                eq(false),
+                eq(testSettingObserver),
+                capture(argumentCaptor)
+            )
+        assertThat(testSettingObserver.value).isEqualTo(5)
+
+        // Verify if the callback applies updated value after the fact
+        whenever(settingsProxy.getInt(any(), any())).thenReturn(12341234)
+        argumentCaptor.value.run()
+        assertThat(testSettingObserver.value).isEqualTo(12341234)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_false_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        reset(settingsProxy)
+        testSettingObserver.isListening = false
+
+        verify(settingsProxy).unregisterContentObserverAsync(eq(testSettingObserver))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_bgFlagDisabled_true_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        verify(settingsProxy)
+            .registerContentObserverSync(any<Uri>(), eq(false), eq(testSettingObserver))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_bgFlagDisabled_false_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        reset(settingsProxy)
+        testSettingObserver.isListening = false
+
+        verify(settingsProxy).unregisterContentObserverSync(eq(testSettingObserver))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index 8ac5b6c..dda9cd5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.settings.userFileManager
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -40,6 +41,70 @@
     private val underTest = with(kosmos) { qsPreferencesRepository }
 
     @Test
+    fun largeTilesSpecs_updatesFromSharedPreferences() =
+        with(kosmos) {
+            testScope.runTest {
+                val latest by collectLastValue(underTest.largeTilesSpecs)
+                assertThat(latest).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+
+                val newSet = setOf("tileA", "tileB")
+                setLargeTilesSpecsInSharedPreferences(newSet)
+                assertThat(latest).isEqualTo(newSet.toTileSpecs())
+            }
+        }
+
+    @Test
+    fun largeTilesSpecs_updatesFromUserChange() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeUserRepository.setUserInfos(USERS)
+                val latest by collectLastValue(underTest.largeTilesSpecs)
+
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+                val newSet = setOf("tileA", "tileB")
+                setLargeTilesSpecsInSharedPreferences(newSet)
+
+                fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
+                setLargeTilesSpecsInSharedPreferences(emptySet())
+
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+                assertThat(latest).isEqualTo(newSet.toTileSpecs())
+            }
+        }
+
+    @Test
+    fun setLargeTilesSpecs_inSharedPreferences() {
+        val setA = setOf("tileA", "tileB")
+        underTest.setLargeTilesSpecs(setA.toTileSpecs())
+        assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
+
+        val setB = setOf("tileA", "tileB")
+        underTest.setLargeTilesSpecs(setB.toTileSpecs())
+        assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
+    }
+
+    @Test
+    fun setLargeTilesSpecs_forDifferentUser() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeUserRepository.setUserInfos(USERS)
+
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+                val setA = setOf("tileA", "tileB")
+                underTest.setLargeTilesSpecs(setA.toTileSpecs())
+                assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
+
+                fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
+                val setB = setOf("tileA", "tileB")
+                underTest.setLargeTilesSpecs(setB.toTileSpecs())
+                assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
+
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+                assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
+            }
+        }
+
+    @Test
     fun showLabels_updatesFromSharedPreferences() =
         with(kosmos) {
             testScope.runTest {
@@ -109,6 +174,14 @@
             )
         }
 
+    private fun setLargeTilesSpecsInSharedPreferences(specs: Set<String>) {
+        getSharedPreferences().edit().putStringSet(LARGE_TILES_SPECS_KEY, specs).apply()
+    }
+
+    private fun getLargeTilesSpecsFromSharedPreferences(): Set<String> {
+        return getSharedPreferences().getStringSet(LARGE_TILES_SPECS_KEY, emptySet())!!
+    }
+
     private fun setShowLabelsInSharedPreferences(value: Boolean) {
         getSharedPreferences().edit().putBoolean(ICON_LABELS_KEY, value).apply()
     }
@@ -117,8 +190,13 @@
         return getSharedPreferences().getBoolean(ICON_LABELS_KEY, defaultValue)
     }
 
+    private fun Set<String>.toTileSpecs(): Set<TileSpec> {
+        return map { TileSpec.create(it) }.toSet()
+    }
+
     companion object {
         private const val ICON_LABELS_KEY = "show_icon_labels"
+        private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
         private const val PRIMARY_USER_ID = 0
         private val PRIMARY_USER = UserInfo(PRIMARY_USER_ID, "user 0", UserInfo.FLAG_MAIN)
         private const val ANOTHER_USER_ID = 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index b206f56..56b3679 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -20,9 +20,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
 import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
 import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
@@ -42,22 +42,17 @@
 @RunWith(AndroidJUnit4::class)
 class GridConsistencyInteractorTest : SysuiTestCase() {
 
-    private val iconOnlyTiles =
-        setOf(
-            TileSpec.create("smallA"),
-            TileSpec.create("smallB"),
-            TileSpec.create("smallC"),
-            TileSpec.create("smallD"),
-            TileSpec.create("smallE"),
-        )
-
     private val kosmos =
         testKosmos().apply {
-            iconTilesRepository =
-                object : IconTilesRepository {
-                    override fun isIconTile(spec: TileSpec): Boolean {
-                        return iconOnlyTiles.contains(spec)
-                    }
+            defaultLargeTilesRepository =
+                object : DefaultLargeTilesRepository {
+                    override val defaultLargeTiles =
+                        setOf(
+                            TileSpec.create("largeA"),
+                            TileSpec.create("largeB"),
+                            TileSpec.create("largeC"),
+                            TileSpec.create("largeD"),
+                        )
                 }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
new file mode 100644
index 0000000..c3a5df06
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 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.qs.panels.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconTilesInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            defaultLargeTilesRepository =
+                object : DefaultLargeTilesRepository {
+                    override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
+                }
+        }
+    private val underTest = with(kosmos) { iconTilesInteractor }
+
+    @Test
+    fun isIconTile_returnsCorrectValue() {
+        assertThat(underTest.isIconTile(TileSpec.create("large"))).isFalse()
+        assertThat(underTest.isIconTile(TileSpec.create("small"))).isTrue()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun isIconTile_updatesFromSharedPreferences() =
+        with(kosmos) {
+            testScope.runTest {
+                // Assert that new tile defaults to icon
+                assertThat(underTest.isIconTile(TileSpec.create("newTile"))).isTrue()
+
+                qsPreferencesRepository.setLargeTilesSpecs(setOf(TileSpec.create("newTile")))
+                runCurrent()
+
+                // Assert that the new tile was added to the large tiles set
+                assertThat(underTest.isIconTile(TileSpec.create("newTile"))).isFalse()
+            }
+        }
+
+    @Test
+    fun resize_updatesSharedPreferences() =
+        with(kosmos) {
+            testScope.runTest {
+                val latest by collectLastValue(qsPreferencesRepository.largeTilesSpecs)
+                val spec = TileSpec.create("large")
+
+                // Assert that the tile is added to the large tiles after resizing
+                underTest.resize(spec, toIcon = false)
+                assertThat(latest).contains(spec)
+
+                // Assert that the tile is removed from the large tiles after resizing
+                underTest.resize(spec, toIcon = true)
+                assertThat(latest).doesNotContain(spec)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
index 1e2e82f..ea51398 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -33,21 +33,17 @@
 @RunWith(AndroidJUnit4::class)
 class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
 
-    private val iconOnlyTiles =
-        setOf(
-            TileSpec.create("smallA"),
-            TileSpec.create("smallB"),
-            TileSpec.create("smallC"),
-            TileSpec.create("smallD"),
-            TileSpec.create("smallE"),
-        )
     private val kosmos =
         testKosmos().apply {
-            iconTilesRepository =
-                object : IconTilesRepository {
-                    override fun isIconTile(spec: TileSpec): Boolean {
-                        return iconOnlyTiles.contains(spec)
-                    }
+            defaultLargeTilesRepository =
+                object : DefaultLargeTilesRepository {
+                    override val defaultLargeTiles: Set<TileSpec> =
+                        setOf(
+                            TileSpec.create("largeA"),
+                            TileSpec.create("largeB"),
+                            TileSpec.create("largeC"),
+                            TileSpec.create("largeD"),
+                        )
                 }
         }
     private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 914a095..53384af 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
 import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
@@ -37,11 +37,9 @@
 class InfiniteGridLayoutTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            iconTilesRepository =
-                object : IconTilesRepository {
-                    override fun isIconTile(spec: TileSpec): Boolean {
-                        return spec.spec.startsWith("small")
-                    }
+            defaultLargeTilesRepository =
+                object : DefaultLargeTilesRepository {
+                    override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
                 }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
index 3354b4d4..55b7454 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
 import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
@@ -37,11 +37,9 @@
 class PartitionedGridLayoutTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            iconTilesRepository =
-                object : IconTilesRepository {
-                    override fun isIconTile(spec: TileSpec): Boolean {
-                        return spec.spec.startsWith("small")
-                    }
+            defaultLargeTilesRepository =
+                object : DefaultLargeTilesRepository {
+                    override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
                 }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
index d36a81b..56156a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -24,8 +24,8 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.res.R
@@ -40,18 +40,6 @@
 @RunWith(AndroidJUnit4::class)
 class QuickQuickSettingsViewModelTest : SysuiTestCase() {
 
-    private val kosmos =
-        testKosmos().apply {
-            iconTilesRepository =
-                object : IconTilesRepository {
-                    override fun isIconTile(spec: TileSpec): Boolean {
-                        return spec.spec.startsWith(PREFIX_SMALL)
-                    }
-                }
-        }
-
-    private val underTest = kosmos.quickQuickSettingsViewModel
-
     private val tiles =
         listOf(
                 "$PREFIX_SMALL:1",
@@ -66,6 +54,17 @@
             )
             .map(TileSpec::create)
 
+    private val kosmos =
+        testKosmos().apply {
+            defaultLargeTilesRepository =
+                object : DefaultLargeTilesRepository {
+                    override val defaultLargeTiles: Set<TileSpec> =
+                        tiles.filter { it.spec.startsWith(PREFIX_LARGE) }.toSet()
+                }
+        }
+
+    private val underTest = kosmos.quickQuickSettingsViewModel
+
     @Before
     fun setUp() {
         kosmos.setTiles(tiles)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
index 89b9b7f..67e2fba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
 
 import android.os.UserHandle
 import android.platform.test.annotations.EnabledOnRavenwood
@@ -23,7 +23,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
index 8982d81..79fcc92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
 
 import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
@@ -26,7 +26,6 @@
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
-import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,7 +53,7 @@
                 connectivityRepository,
                 mobileConnectionsRepository,
             ),
-            inputHandler
+            inputHandler,
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
index 2e5fde8..a5f98a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
@@ -30,7 +30,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.toCollection
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -56,8 +55,7 @@
     @Test
     fun alwaysAvailable() =
         testScope.runTest {
-            val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
-
+            val availability by collectValues(underTest.availability(TEST_USER))
             assertThat(availability).hasSize(1)
             assertThat(availability.last()).isEqualTo(isAvailable)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
index 6ea5e63..3133312 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
@@ -17,9 +17,13 @@
 package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
 
 import android.platform.test.annotations.EnabledOnRavenwood
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.server.display.feature.flags.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.reduceBrightColorsController
 import com.android.systemui.kosmos.Kosmos
@@ -43,11 +47,22 @@
 
     private val underTest =
         ReduceBrightColorsTileUserActionInteractor(
+            context.resources,
+            inputHandler,
+            controller,
+        )
+
+    private val underTestEvenDimmerEnabled =
+        ReduceBrightColorsTileUserActionInteractor(
+            context.orCreateTestableResources
+                .apply { addOverride(R.bool.config_evenDimmerEnabled, true) }
+                .resources,
             inputHandler,
             controller,
         )
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
     fun handleClickWhenEnabled() = runTest {
         val wasEnabled = true
         controller.isReduceBrightColorsActivated = wasEnabled
@@ -58,6 +73,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
     fun handleClickWhenDisabled() = runTest {
         val wasEnabled = false
         controller.isReduceBrightColorsActivated = wasEnabled
@@ -68,6 +84,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
     fun handleLongClickWhenDisabled() = runTest {
         val enabled = false
 
@@ -79,6 +96,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
     fun handleLongClickWhenEnabled() = runTest {
         val enabled = true
 
@@ -88,4 +106,58 @@
             assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
         }
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    fun handleClickWhenEnabledEvenDimmer() = runTest {
+        val wasEnabled = true
+        controller.isReduceBrightColorsActivated = wasEnabled
+
+        underTestEvenDimmerEnabled.handleInput(
+            QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled))
+        )
+
+        assertThat(controller.isReduceBrightColorsActivated).isEqualTo(wasEnabled)
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    fun handleClickWhenDisabledEvenDimmer() = runTest {
+        val wasEnabled = false
+        controller.isReduceBrightColorsActivated = wasEnabled
+
+        underTestEvenDimmerEnabled.handleInput(
+            QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled))
+        )
+
+        assertThat(controller.isReduceBrightColorsActivated).isEqualTo(wasEnabled)
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    fun handleLongClickWhenDisabledEvenDimmer() = runTest {
+        val enabled = false
+
+        underTestEvenDimmerEnabled.handleInput(
+            QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled))
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_DISPLAY_SETTINGS)
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    fun handleLongClickWhenEnabledEvenDimmer() = runTest {
+        val enabled = true
+
+        underTestEvenDimmerEnabled.handleInput(
+            QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled))
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_DISPLAY_SETTINGS)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index b35b7bc..09580c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.settings.brightness.MirrorController
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
@@ -558,7 +557,7 @@
     fun dispatchSplitShade() =
         testScope.runTest {
             val shadeRepository = kosmos.fakeShadeRepository
-            shadeRepository.setShadeMode(ShadeMode.Single)
+            shadeRepository.setShadeLayoutWide(false)
             val qsImpl by collectLastValue(underTest.qsImpl)
 
             underTest.inflate(context)
@@ -566,7 +565,7 @@
 
             verify(qsImpl!!).setInSplitShade(false)
 
-            shadeRepository.setShadeMode(ShadeMode.Split)
+            shadeRepository.setShadeLayoutWide(true)
             runCurrent()
             verify(qsImpl!!).setInSplitShade(true)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
index 545a0c7..0ab6a82 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,7 +62,7 @@
     fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
-            shadeRepository.setShadeMode(ShadeMode.Split)
+            shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey)
@@ -74,7 +73,7 @@
     fun downTransitionKey_splitShadeDisabled_isNull() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
-            shadeRepository.setShadeMode(ShadeMode.Single)
+            shadeRepository.setShadeLayoutWide(false)
             runCurrent()
 
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 78c4def..3283ea1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -19,6 +19,8 @@
 import android.app.StatusBarManager.DISABLE2_NONE
 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -37,7 +39,10 @@
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.phone.dozeParameters
@@ -452,4 +457,44 @@
             val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
             assertThat(isShadeTouchable).isTrue()
         }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun legacyShadeMode_narrowScreen_singleShade() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun legacyShadeMode_wideScreen_splitShade() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun shadeMode_wideScreen_isDual() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun shadeMode_narrowScreen_isDual() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index b1bffdb..8a43198 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
@@ -568,21 +567,6 @@
             sceneInteractor.setTransitionState(transitionState)
 
             // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-        }
-
-    @Test
-    fun shadeMode() =
-        testScope.runTest {
-            val shadeMode by collectLastValue(underTest.shadeMode)
-
-            shadeRepository.setShadeMode(ShadeMode.Split)
-            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
-
-            shadeRepository.setShadeMode(ShadeMode.Single)
-            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
-
-            shadeRepository.setShadeMode(ShadeMode.Split)
-            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+            assertThat(interacting).isFalse()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 673d5ef..da22c6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -38,7 +39,6 @@
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
-import com.android.systemui.qs.ui.adapter.qsSceneAdapter
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
@@ -47,6 +47,7 @@
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
@@ -66,6 +67,7 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
+@DisableFlags(DualShade.FLAG_NAME)
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -157,7 +159,7 @@
     fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
-            shadeRepository.setShadeMode(ShadeMode.Split)
+            shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey)
@@ -168,7 +170,7 @@
     fun upTransitionKey_splitShadeDisable_isNull() =
         testScope.runTest {
             val destinationScenes by collectLastValue(underTest.destinationScenes)
-            shadeRepository.setShadeMode(ShadeMode.Single)
+            shadeRepository.setShadeLayoutWide(false)
             runCurrent()
 
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
@@ -268,13 +270,13 @@
         testScope.runTest {
             val shadeMode by collectLastValue(underTest.shadeMode)
 
-            shadeRepository.setShadeMode(ShadeMode.Split)
+            shadeRepository.setShadeLayoutWide(true)
             assertThat(shadeMode).isEqualTo(ShadeMode.Split)
 
-            shadeRepository.setShadeMode(ShadeMode.Single)
+            shadeRepository.setShadeLayoutWide(false)
             assertThat(shadeMode).isEqualTo(ShadeMode.Single)
 
-            shadeRepository.setShadeMode(ShadeMode.Split)
+            shadeRepository.setShadeLayoutWide(true)
             assertThat(shadeMode).isEqualTo(ShadeMode.Split)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index 50b77dc..1356e93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
 import com.android.systemui.testKosmos
@@ -66,11 +65,11 @@
         testScope.runTest {
             val stackRounding by collectLastValue(underTest.shadeScrimRounding)
 
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
             assertThat(stackRounding)
                 .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false))
 
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             assertThat(stackRounding)
                 .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true))
         }
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index b09d35d..5191895 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -60,6 +60,7 @@
         android:layout_marginStart="16dp"
         android:checked="true"
         android:text="@string/backlinks_include_link"
+        android:textColor="?android:textColorSecondary"
         android:visibility="gone"
         app:layout_constraintBottom_toTopOf="@id/preview"
         app:layout_constraintStart_toEndOf="@id/cancel"
@@ -74,6 +75,7 @@
         android:drawablePadding="4dp"
         android:gravity="center"
         android:paddingHorizontal="8dp"
+        android:textColor="?android:textColorSecondary"
         android:visibility="gone"
         app:layout_constraintBottom_toTopOf="@id/preview"
         app:layout_constraintStart_toEndOf="@id/backlinks_include_data"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 2cfd644..45a4af9 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -85,7 +85,7 @@
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
+            android:layout_gravity="center_vertical|start"
             android:orientation="horizontal">
             <Button
                 android:id="@+id/shortcut_system"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ca55c23..0350cd7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -100,8 +100,8 @@
     <!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
     <color name="navigation_bar_icon_color">#E5FFFFFF</color>
 
-    <color name="white">@*android:color/white</color>
-    <color name="black">@*android:color/black</color>
+    <color name="navigation_bar_home_handle_light_color">#EBffffff</color>
+    <color name="navigation_bar_home_handle_dark_color">#99000000</color>
 
     <!-- The shadow color for light navigation bar icons. -->
     <color name="nav_key_button_shadow_color">#30000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 84d5dcb..80b9ec7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -56,7 +56,7 @@
          enabled for OLED devices to reduce/prevent burn in on the navigation bar (because of the
          black background and static button placements) and disabled for all other devices to
          prevent wasting cpu cycles on the dimming animation -->
-    <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">false</bool>
+    <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
 
     <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_tiles">4</integer>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index dee5528..a6dd14a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2050,6 +2050,8 @@
     <string name="keyboard_key_page_down">Page Down</string>
     <!-- Name used to refer to the "Delete" key on the keyboard. -->
     <string name="keyboard_key_forward_del">Delete</string>
+    <!-- Name used to refer to the "Esc" or "Escape" key on the keyboard. -->
+    <string name="keyboard_key_esc">Esc</string>
     <!-- Name used to refer to the "Home" move key on the keyboard. -->
     <string name="keyboard_key_move_home">Home</string>
     <!-- Name used to refer to the "End" move key on the keyboard. -->
@@ -3595,6 +3597,10 @@
          that shows the user which keyboard shortcuts they can use. The "App shortcuts" are
          for example "Open browser" or "Open calculator". [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_category_app_shortcuts">App shortcuts</string>
+    <!-- Default Title of the keyboard shortcut helper category for current app. The helper is a
+         component that shows the user which keyboard shortcuts they can use. The current app
+         shortcuts are shortcuts provided by the currently open app. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_category_current_app_shortcuts">Current App</string>
     <!-- Title of the keyboard shortcut helper category "Accessibility". The helper is a component
          that shows the user which keyboard shortcuts they can use. The "Accessibility" shortcuts
          are for example "Turn on talkback". [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 047578c..7475eb2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -622,14 +622,14 @@
     <style name="DualToneLightTheme">
         <item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
-        <item name="singleToneColor">@color/white</item>
-        <item name="homeHandleColor">@color/white</item>
+        <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
+        <item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
     </style>
     <style name="DualToneDarkTheme">
         <item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
         <item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
-        <item name="singleToneColor">@color/black</item>
-        <item name="homeHandleColor">@color/black</item>
+        <item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
+        <item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
     </style>
     <style name="QSHeaderDarkTheme">
         <item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
@@ -648,7 +648,7 @@
         <item name="singleToneColor">?android:attr/textColorPrimary</item>
     </style>
     <style name="ScreenPinningRequestTheme" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent">
-        <item name="singleToneColor">@color/white</item>
+        <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
     </style>
 
     <style name="TextAppearance.Volume">
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index fe61c46..eb0aae9 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -33,6 +33,7 @@
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             android:layout_marginStart="8dp"
+            app:layout_constraintBaseline_toBaselineOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toEndOf="@id/clock"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a42f4c2d..baf8f5a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -54,7 +54,6 @@
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 
 import dagger.Lazy;
@@ -129,7 +128,6 @@
     @Nullable
     @Inject Lazy<VolumeDialogController> mVolumeDialogController;
     @Inject Lazy<MetricsLogger> mMetricsLogger;
-    @Inject Lazy<TunablePaddingService> mTunablePaddingService;
     @Inject Lazy<UiOffloadThread> mUiOffloadThread;
     @Inject Lazy<LightBarController> mLightBarController;
     @Inject Lazy<OverviewProxyService> mOverviewProxyService;
@@ -177,7 +175,6 @@
         mProviders.put(FragmentService.class, mFragmentService::get);
         mProviders.put(VolumeDialogController.class, mVolumeDialogController::get);
         mProviders.put(MetricsLogger.class, mMetricsLogger::get);
-        mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
         mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
         mProviders.put(LightBarController.class, mLightBarController::get);
         mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
new file mode 100644
index 0000000..78016c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.communal
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class CommunalOngoingContentStartable
+@Inject
+constructor(
+    @Background val bgScope: CoroutineScope,
+    private val communalInteractor: CommunalInteractor,
+    private val communalMediaRepository: CommunalMediaRepository,
+    private val communalSmartspaceRepository: CommunalSmartspaceRepository,
+    private val featureFlags: FeatureFlagsClassic,
+) : CoreStartable {
+
+    override fun start() {
+        if (
+            !featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) ||
+                !com.android.systemui.Flags.communalHub()
+        ) {
+            return
+        }
+
+        bgScope.launch {
+            communalInteractor.isCommunalEnabled.collect { enabled ->
+                if (enabled) {
+                    communalMediaRepository.startListening()
+                    communalSmartspaceRepository.startListening()
+                } else {
+                    communalMediaRepository.stopListening()
+                    communalSmartspaceRepository.stopListening()
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 2406cc6..3d201a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -52,6 +53,8 @@
             CommunalWidgetModule::class,
             CommunalPrefsRepositoryModule::class,
             CommunalSettingsRepositoryModule::class,
+            CommunalSmartspaceRepositoryModule::class,
+            CommunalStartableModule::class,
         ]
 )
 interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
new file mode 100644
index 0000000..74a2cd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.communal.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.CommunalBackupRestoreStartable
+import com.android.systemui.communal.CommunalDreamStartable
+import com.android.systemui.communal.CommunalOngoingContentStartable
+import com.android.systemui.communal.CommunalSceneStartable
+import com.android.systemui.communal.log.CommunalLoggerStartable
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface CommunalStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalLoggerStartable::class)
+    fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalSceneStartable::class)
+    fun bindCommunalSceneStartable(impl: CommunalSceneStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalDreamStartable::class)
+    fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalAppWidgetHostStartable::class)
+    fun bindCommunalAppWidgetHostStartable(impl: CommunalAppWidgetHostStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalBackupRestoreStartable::class)
+    fun bindCommunalBackupRestoreStartable(impl: CommunalBackupRestoreStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalOngoingContentStartable::class)
+    fun bindCommunalOngoingContentStartable(impl: CommunalOngoingContentStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt
new file mode 100644
index 0000000..ff9dddd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.communal.data.model
+
+import android.widget.RemoteViews
+
+/** Data model of a smartspace timer in the Glanceable Hub. */
+data class CommunalSmartspaceTimer(
+    /** Unique id that identifies the timer. */
+    val smartspaceTargetId: String,
+    /** Timestamp in milliseconds of when the timer was created. */
+    val createdTimestampMillis: Long,
+    /** Remote views for the timer that is rendered in Glanceable Hub. */
+    val remoteViews: RemoteViews,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e5a0e50..fe9154c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -30,6 +30,12 @@
 /** Encapsulates the state of smartspace in communal. */
 interface CommunalMediaRepository {
     val mediaModel: Flow<CommunalMediaModel>
+
+    /** Start listening for media updates. */
+    fun startListening()
+
+    /** Stop listening for media updates. */
+    fun stopListening()
 }
 
 @SysUISingleton
@@ -38,29 +44,7 @@
 constructor(
     private val mediaDataManager: MediaDataManager,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
-) : CommunalMediaRepository {
-
-    private val mediaDataListener =
-        object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(
-                key: String,
-                oldKey: String?,
-                data: MediaData,
-                immediately: Boolean,
-                receivedSmartspaceCardLatency: Int,
-                isSsReactivated: Boolean
-            ) {
-                updateMediaModel(data)
-            }
-
-            override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
-                updateMediaModel()
-            }
-        }
-
-    init {
-        mediaDataManager.addListener(mediaDataListener)
-    }
+) : CommunalMediaRepository, MediaDataManager.Listener {
 
     private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
         MutableStateFlow(CommunalMediaModel.INACTIVE)
@@ -72,6 +56,29 @@
             initialValue = CommunalMediaModel.INACTIVE,
         )
 
+    override fun startListening() {
+        mediaDataManager.addListener(this)
+    }
+
+    override fun stopListening() {
+        mediaDataManager.removeListener(this)
+    }
+
+    override fun onMediaDataLoaded(
+        key: String,
+        oldKey: String?,
+        data: MediaData,
+        immediately: Boolean,
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
+    ) {
+        updateMediaModel(data)
+    }
+
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+        updateMediaModel()
+    }
+
     private fun updateMediaModel(data: MediaData? = null) {
         if (mediaDataManager.hasActiveMediaOrRecommendation()) {
             _mediaModel.value =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
new file mode 100644
index 0000000..e1d9bef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 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.communal.data.repository
+
+import android.app.smartspace.SmartspaceTarget
+import android.os.Parcelable
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.util.time.SystemClock
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+interface CommunalSmartspaceRepository {
+    /** Smartspace timer targets for the communal surface. */
+    val timers: Flow<List<CommunalSmartspaceTimer>>
+
+    /** Start listening for smartspace updates. */
+    fun startListening()
+
+    /** Stop listening for smartspace updates. */
+    fun stopListening()
+}
+
+@SysUISingleton
+class CommunalSmartspaceRepositoryImpl
+@Inject
+constructor(
+    private val communalSmartspaceController: CommunalSmartspaceController,
+    @Main private val uiExecutor: Executor,
+    private val systemClock: SystemClock,
+) : CommunalSmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+
+    private val _timers: MutableStateFlow<List<CommunalSmartspaceTimer>> =
+        MutableStateFlow(emptyList())
+    override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers
+
+    private var targetCreationTimes = emptyMap<String, Long>()
+
+    override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
+        val targets = targetsNullable?.filterIsInstance<SmartspaceTarget>() ?: emptyList()
+        val timerTargets =
+            targets
+                .filter { target ->
+                    target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+                        target.remoteViews != null
+                }
+                .associateBy { stableId(it.smartspaceTargetId) }
+
+        // The creation times from smartspace targets are unreliable (b/318535930). Therefore,
+        // SystemUI uses the timestamp of which a timer first appears, and caches these values to
+        // prevent timers from swapping positions in the hub.
+        targetCreationTimes =
+            timerTargets.mapValues { (stableId, _) ->
+                targetCreationTimes[stableId] ?: systemClock.currentTimeMillis()
+            }
+
+        _timers.value =
+            timerTargets.map { (stableId, target) ->
+                CommunalSmartspaceTimer(
+                    // The view layer should have the instance based smartspaceTargetId instead of
+                    // stable id, so that when a new instance of the timer is created, for example,
+                    // when it is paused, the view should re-render its remote views.
+                    smartspaceTargetId = target.smartspaceTargetId,
+                    createdTimestampMillis = targetCreationTimes[stableId]!!,
+                    remoteViews = target.remoteViews!!,
+                )
+            }
+    }
+
+    override fun startListening() {
+        if (android.app.smartspace.flags.Flags.remoteViews()) {
+            uiExecutor.execute {
+                communalSmartspaceController.addListener(
+                    listener = this@CommunalSmartspaceRepositoryImpl
+                )
+            }
+        }
+    }
+
+    override fun stopListening() {
+        uiExecutor.execute {
+            communalSmartspaceController.removeListener(
+                listener = this@CommunalSmartspaceRepositoryImpl
+            )
+        }
+    }
+
+    companion object {
+        /**
+         * The smartspace target id is instance-based, meaning a single timer (from the user's
+         * perspective) can have multiple instances. For example, when a timer is paused, a new
+         * instance is created. To address this, SystemUI manually removes the instance id to
+         * maintain a consistent id across sessions.
+         *
+         * It is assumed that timer target ids follow this format: timer-${stableId}-${instanceId}.
+         * This function returns timer-${stableId}, stripping out the instance id.
+         */
+        @VisibleForTesting
+        fun stableId(targetId: String): String {
+            return targetId.split("-").take(2).joinToString("-")
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt
index c92234c..b11c6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.communal.data.repository
 
-import javax.inject.Qualifier
+import dagger.Binds
+import dagger.Module
 
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+@Module
+interface CommunalSmartspaceRepositoryModule {
+    @Binds
+    fun communalSmartspaceRepository(
+        impl: CommunalSmartspaceRepositoryImpl
+    ): CommunalSmartspaceRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 86f5fe1..597a2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.communal.domain.interactor
 
-import android.app.smartspace.SmartspaceTarget
 import android.content.ComponentName
 import android.content.Intent
 import android.content.IntentFilter
@@ -29,6 +28,7 @@
 import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
@@ -60,7 +60,6 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.emitOnStart
@@ -82,7 +81,6 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -101,7 +99,7 @@
     private val widgetRepository: CommunalWidgetRepository,
     private val communalPrefsInteractor: CommunalPrefsInteractor,
     private val mediaRepository: CommunalMediaRepository,
-    smartspaceRepository: SmartspaceRepository,
+    private val smartspaceRepository: CommunalSmartspaceRepository,
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     communalSettingsInteractor: CommunalSettingsInteractor,
@@ -435,19 +433,6 @@
             }
         }
 
-    /** A flow of available smartspace targets. Currently only showing timers. */
-    private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
-        if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
-            flowOf(emptyList())
-        } else {
-            smartspaceRepository.communalSmartspaceTargets.map { targets ->
-                targets.filter { target ->
-                    target.featureType == SmartspaceTarget.FEATURE_TIMER &&
-                        target.remoteViews != null
-                }
-            }
-        }
-
     /** CTA tile to be displayed in the glanceable hub (view mode). */
     val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
         communalPrefsInteractor.isCtaDismissed.map { isDismissed ->
@@ -472,16 +457,16 @@
      * sized dynamically.
      */
     fun getOngoingContent(mediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> =
-        combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media ->
+        combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media ->
                 val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
 
-                // Add smartspace
+                // Add smartspace timers
                 ongoingContent.addAll(
-                    smartspace.map { target ->
+                    timers.map { timer ->
                         CommunalContentModel.Smartspace(
-                            smartspaceTargetId = target.smartspaceTargetId,
-                            remoteViews = target.remoteViews!!,
-                            createdTimestampMillis = target.creationTimeMillis,
+                            smartspaceTargetId = timer.smartspaceTargetId,
+                            remoteViews = timer.remoteViews,
+                            createdTimestampMillis = timer.createdTimestampMillis,
                         )
                     }
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b0fc60e..a6deca7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.dagger;
 
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
@@ -111,6 +114,9 @@
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 import androidx.core.app.NotificationManagerCompat;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
+import com.android.app.viewcapture.ViewCaptureFactory;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -125,6 +131,7 @@
 import com.android.systemui.user.utils.UserScopedService;
 import com.android.systemui.user.utils.UserScopedServiceImpl;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -680,6 +687,15 @@
 
     @Provides
     @Singleton
+    static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
+            WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) {
+        return new ViewCaptureAwareWindowManager(windowManager,
+                /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
+                /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+    }
+
+    @Provides
+    @Singleton
     static PermissionManager providePermissionManager(Context context) {
         PermissionManager pm = context.getSystemService(PermissionManager.class);
         if (pm != null) {
@@ -764,4 +780,10 @@
         return IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
     }
+
+    @Provides
+    @Singleton
+    static ViewCapture provideViewCapture(Context context) {
+        return ViewCaptureFactory.getInstance(context);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 593196c..88601da 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,11 +24,6 @@
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
-import com.android.systemui.communal.CommunalDreamStartable
-import com.android.systemui.communal.CommunalBackupRestoreStartable
-import com.android.systemui.communal.CommunalSceneStartable
-import com.android.systemui.communal.log.CommunalLoggerStartable
-import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
 import com.android.systemui.controls.dagger.StartControlsStartableModule
 import com.android.systemui.dagger.qualifiers.PerUser
 import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -80,12 +75,13 @@
  * @deprecated
  */
 @Module(
-    includes = [
-        MultiUserUtilsModule::class,
-        StartControlsStartableModule::class,
-        StartBinderLoggerModule::class,
-        WallpaperModule::class,
-    ]
+    includes =
+        [
+            MultiUserUtilsModule::class,
+            StartControlsStartableModule::class,
+            StartBinderLoggerModule::class,
+            WallpaperModule::class,
+        ]
 )
 abstract class SystemUICoreStartableModule {
     /** Inject into BiometricNotificationService */
@@ -96,25 +92,25 @@
         service: BiometricNotificationService
     ): CoreStartable
 
-    /** Inject into ClipboardListener.  */
+    /** Inject into ClipboardListener. */
     @Binds
     @IntoMap
     @ClassKey(ClipboardListener::class)
     abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable
 
-    /** Inject into GlobalActionsComponent.  */
+    /** Inject into GlobalActionsComponent. */
     @Binds
     @IntoMap
     @ClassKey(GlobalActionsComponent::class)
     abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
 
-    /** Inject into InstantAppNotifier.  */
+    /** Inject into InstantAppNotifier. */
     @Binds
     @IntoMap
     @ClassKey(InstantAppNotifier::class)
     abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
 
-    /** Inject into KeyboardUI.  */
+    /** Inject into KeyboardUI. */
     @Binds
     @IntoMap
     @ClassKey(KeyboardUI::class)
@@ -125,7 +121,7 @@
     @IntoMap
     @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
     abstract fun bindProjectedTaskListener(
-            sysui: MediaProjectionTaskSwitcherCoreStartable
+        sysui: MediaProjectionTaskSwitcherCoreStartable
     ): CoreStartable
 
     /** Inject into KeyguardBiometricLockoutLogger */
@@ -136,38 +132,38 @@
         sysui: KeyguardBiometricLockoutLogger
     ): CoreStartable
 
-    /** Inject into KeyguardViewMediator.  */
+    /** Inject into KeyguardViewMediator. */
     @Binds
     @IntoMap
     @ClassKey(KeyguardViewMediator::class)
     abstract fun bindKeyguardViewMediator(sysui: KeyguardViewMediator): CoreStartable
 
-    /** Inject into LatencyTests.  */
+    /** Inject into LatencyTests. */
     @Binds
     @IntoMap
     @ClassKey(LatencyTester::class)
     abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
 
-    /** Inject into DisplaySwitchLatencyTracker.  */
+    /** Inject into DisplaySwitchLatencyTracker. */
     @Binds
     @IntoMap
     @ClassKey(DisplaySwitchLatencyTracker::class)
     abstract fun bindDisplaySwitchLatencyTracker(sysui: DisplaySwitchLatencyTracker): CoreStartable
 
-    /** Inject into NotificationChannels.  */
+    /** Inject into NotificationChannels. */
     @Binds
     @IntoMap
     @ClassKey(NotificationChannels::class)
     @PerUser
     abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
 
-    /** Inject into ImmersiveModeConfirmation.  */
+    /** Inject into ImmersiveModeConfirmation. */
     @Binds
     @IntoMap
     @ClassKey(ImmersiveModeConfirmation::class)
     abstract fun bindImmersiveModeConfirmation(sysui: ImmersiveModeConfirmation): CoreStartable
 
-    /** Inject into RingtonePlayer.  */
+    /** Inject into RingtonePlayer. */
     @Binds
     @IntoMap
     @ClassKey(RingtonePlayer::class)
@@ -179,50 +175,49 @@
     @ClassKey(GesturePointerEventListener::class)
     abstract fun bindGesturePointerEventListener(sysui: GesturePointerEventListener): CoreStartable
 
-    /** Inject into SessionTracker.  */
+    /** Inject into SessionTracker. */
     @Binds
     @IntoMap
     @ClassKey(SessionTracker::class)
     abstract fun bindSessionTracker(service: SessionTracker): CoreStartable
 
-    /** Inject into ShortcutKeyDispatcher.  */
+    /** Inject into ShortcutKeyDispatcher. */
     @Binds
     @IntoMap
     @ClassKey(ShortcutKeyDispatcher::class)
     abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
 
-    /** Inject into SliceBroadcastRelayHandler.  */
+    /** Inject into SliceBroadcastRelayHandler. */
     @Binds
     @IntoMap
     @ClassKey(SliceBroadcastRelayHandler::class)
     abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
 
-    /** Inject into StorageNotification.  */
+    /** Inject into StorageNotification. */
     @Binds
     @IntoMap
     @ClassKey(StorageNotification::class)
     abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
 
-    /** Inject into ThemeOverlayController.  */
+    /** Inject into ThemeOverlayController. */
     @Binds
     @IntoMap
     @ClassKey(ThemeOverlayController::class)
     abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
 
-
-    /** Inject into MediaOutputSwitcherDialogUI.  */
+    /** Inject into MediaOutputSwitcherDialogUI. */
     @Binds
     @IntoMap
     @ClassKey(MediaOutputSwitcherDialogUI::class)
     abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
 
-    /** Inject into Magnification.  */
+    /** Inject into Magnification. */
     @Binds
     @IntoMap
     @ClassKey(Magnification::class)
     abstract fun bindMagnification(sysui: Magnification): CoreStartable
 
-    /** Inject into WMShell.  */
+    /** Inject into WMShell. */
     @Binds
     @IntoMap
     @ClassKey(WMShell::class)
@@ -239,7 +234,7 @@
     @IntoMap
     @ClassKey(MediaTttChipControllerReceiver::class)
     abstract fun bindMediaTttChipControllerReceiver(
-            sysui: MediaTttChipControllerReceiver
+        sysui: MediaTttChipControllerReceiver
     ): CoreStartable
 
     /** Inject into MediaTttCommandLineHelper. */
@@ -254,8 +249,6 @@
     @ClassKey(ChipbarCoordinator::class)
     abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
 
-
-
     /** Inject into StylusUsiPowerStartable) */
     @Binds
     @IntoMap
@@ -267,21 +260,21 @@
     @ClassKey(PhysicalKeyboardCoreStartable::class)
     abstract fun bindKeyboardCoreStartable(listener: PhysicalKeyboardCoreStartable): CoreStartable
 
-    /** Inject into MuteQuickAffordanceCoreStartable*/
+    /** Inject into MuteQuickAffordanceCoreStartable */
     @Binds
     @IntoMap
     @ClassKey(MuteQuickAffordanceCoreStartable::class)
     abstract fun bindMuteQuickAffordanceCoreStartable(
-            sysui: MuteQuickAffordanceCoreStartable
+        sysui: MuteQuickAffordanceCoreStartable
     ): CoreStartable
 
-    /**Inject into DreamMonitor */
+    /** Inject into DreamMonitor */
     @Binds
     @IntoMap
     @ClassKey(DreamMonitor::class)
     abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
 
-    /**Inject into AssistantAttentionMonitor */
+    /** Inject into AssistantAttentionMonitor */
     @Binds
     @IntoMap
     @ClassKey(AssistantAttentionMonitor::class)
@@ -321,35 +314,6 @@
 
     @Binds
     @IntoMap
-    @ClassKey(CommunalLoggerStartable::class)
-    abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
-
-    @Binds
-    @IntoMap
-    @ClassKey(CommunalSceneStartable::class)
-    abstract fun bindCommunalSceneStartable(impl: CommunalSceneStartable): CoreStartable
-
-    @Binds
-    @IntoMap
-    @ClassKey(CommunalDreamStartable::class)
-    abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
-
-    @Binds
-    @IntoMap
-    @ClassKey(CommunalAppWidgetHostStartable::class)
-    abstract fun bindCommunalAppWidgetHostStartable(
-        impl: CommunalAppWidgetHostStartable
-    ): CoreStartable
-
-    @Binds
-    @IntoMap
-    @ClassKey(CommunalBackupRestoreStartable::class)
-    abstract fun bindCommunalBackupRestoreStartable(
-        impl: CommunalBackupRestoreStartable
-    ): CoreStartable
-
-    @Binds
-    @IntoMap
     @ClassKey(HomeControlsDreamStartable::class)
     abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): 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 0bcfa96..69ddb62 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
@@ -44,6 +44,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -137,6 +138,7 @@
     override val displayAdditionEvent: Flow<Display?> =
         allDisplayEvents.filterIsInstance<DisplayEvent.Added>().map { getDisplay(it.displayId) }
 
+    // TODO: b/345472038 - Delete after the flag is ramped up.
     private val oldEnabledDisplays: Flow<Set<Display>> =
         allDisplayEvents
             .map { getDisplays() }
@@ -167,6 +169,9 @@
             }
             .debugLog("enabledDisplayIds")
 
+    private val defaultDisplay by lazy {
+        getDisplay(Display.DEFAULT_DISPLAY) ?: error("Unable to get default display.")
+    }
     /**
      * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
      *
@@ -181,11 +186,7 @@
                 .stateIn(
                     bgApplicationScope,
                     started = SharingStarted.WhileSubscribed(),
-                    initialValue =
-                        setOf(
-                            getDisplay(Display.DEFAULT_DISPLAY)
-                                ?: error("Unable to get default display.")
-                        )
+                    initialValue = setOf(defaultDisplay)
                 )
         } else {
             oldEnabledDisplays
@@ -344,9 +345,10 @@
             .debugLog("pendingDisplay")
 
     override val defaultDisplayOff: Flow<Boolean> =
-        displays
-            .map { displays -> displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY } }
-            .map { it?.state == Display.STATE_OFF }
+        displayChangeEvent
+            .filter { it == Display.DEFAULT_DISPLAY }
+            .map { defaultDisplay.state == Display.STATE_OFF }
+            .distinctUntilChanged()
 
     private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
         return if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index c6c57479..83fa001 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -45,6 +45,7 @@
 import androidx.lifecycle.ServiceLifecycleDispatcher;
 import androidx.lifecycle.ViewModelStore;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.dream.lowlight.dagger.LowLightDreamModule;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -97,7 +98,7 @@
     @Nullable
     private final ComponentName mHomeControlPanelDreamComponent;
     private final UiEventLogger mUiEventLogger;
-    private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mWindowManager;
     private final String mWindowTitle;
 
     // A reference to the {@link Window} used to hold the dream overlay.
@@ -244,7 +245,7 @@
             Context context,
             DreamOverlayLifecycleOwner lifecycleOwner,
             @Main DelayableExecutor executor,
-            WindowManager windowManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             ComplicationComponent.Factory complicationComponentFactory,
             com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
                     dreamComplicationComponentFactory,
@@ -267,7 +268,7 @@
         super(executor);
         mContext = context;
         mExecutor = executor;
-        mWindowManager = windowManager;
+        mWindowManager = viewCaptureAwareWindowManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mScrimManager = scrimManager;
         mLowLightDreamComponent = lowLightDreamComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 7b5139a..c44eb47 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.haptics.qs
 
 import android.os.VibrationEffect
+import android.service.quicksettings.Tile
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.animation.Expandable
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,6 +42,7 @@
 constructor(
     private val vibratorHelper: VibratorHelper?,
     private val keyguardStateController: KeyguardStateController,
+    private val falsingManager: FalsingManager,
 ) {
 
     var effectDuration = 0
@@ -130,18 +133,18 @@
     fun handleAnimationComplete() {
         when (state) {
             State.RUNNING_FORWARD -> {
-                setState(State.IDLE)
                 vibrate(snapEffect)
                 if (keyguardStateController.isUnlocked) {
-                    qsTile?.longClick(expandable)
+                    setState(State.LONG_CLICKED)
                 } else {
                     callback?.onResetProperties()
-                    qsTile?.longClick(expandable)
+                    setState(State.IDLE)
                 }
+                qsTile?.longClick(expandable)
             }
             State.RUNNING_BACKWARDS_FROM_UP -> {
-                setState(State.IDLE)
                 callback?.onEffectFinishedReversing()
+                setState(getStateForClick())
                 qsTile?.click(expandable)
             }
             State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
@@ -160,14 +163,37 @@
     }
 
     fun onTileClick(): Boolean {
-        if (state == State.TIMEOUT_WAIT) {
-            setState(State.IDLE)
-            qsTile?.let {
-                it.click(expandable)
-                return true
-            }
+        val isStateClickable = state == State.TIMEOUT_WAIT || state == State.IDLE
+
+        // Ignore View-generated clicks on invalid states or if the bouncer is showing
+        if (keyguardStateController.isPrimaryBouncerShowing || !isStateClickable) return false
+
+        setState(getStateForClick())
+        qsTile?.click(expandable)
+        return true
+    }
+
+    /**
+     * Get the appropriate state for a click action.
+     *
+     * In some occasions, the click action will not result in a subsequent action that resets the
+     * state upon completion (e.g., a launch transition animation). In these cases, the state needs
+     * to be reset before the click is dispatched.
+     */
+    @VisibleForTesting
+    fun getStateForClick(): State {
+        val isTileUnavailable = qsTile?.state?.state == Tile.STATE_UNAVAILABLE
+        val isFalseTapWhileLocked =
+            !keyguardStateController.isUnlocked &&
+                falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+        val handlesLongClick = qsTile?.state?.handlesLongClick == true
+        return if (isTileUnavailable || isFalseTapWhileLocked || !handlesLongClick) {
+            // The click event will not perform an action that resets the state. Therefore, this is
+            // the last opportunity to reset the state back to IDLE.
+            State.IDLE
+        } else {
+            State.CLICKED
         }
-        return false
     }
 
     /**
@@ -194,6 +220,8 @@
         return true
     }
 
+    fun resetState() = setState(State.IDLE)
+
     enum class State {
         IDLE, /* The effect is idle waiting for touch input */
         TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
@@ -202,6 +230,8 @@
         RUNNING_BACKWARDS_FROM_UP,
         /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
         RUNNING_BACKWARDS_FROM_CANCEL,
+        CLICKED, /* The effect has ended with a click */
+        LONG_CLICKED, /* The effect has ended with a long-click */
     }
 
     /** Callbacks to notify view and animator actions */
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
index 1f0aef8..906f600 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
@@ -21,11 +21,13 @@
 import com.android.systemui.Flags.keyboardShortcutHelperRewrite
 import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
 import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
 import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
@@ -55,6 +57,10 @@
     fun multitaskingShortcutsSource(impl: MultitaskingShortcutsSource): KeyboardShortcutGroupsSource
 
     @Binds
+    @CurrentAppShortcuts
+    fun currentAppShortcutsSource(impl: CurrentAppShortcutsSource): KeyboardShortcutGroupsSource
+
+    @Binds
     @InputShortcuts
     fun inputShortcutsSources(impl: InputShortcutsSource): KeyboardShortcutGroupsSource
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 7b0c25e..9e53792 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -28,16 +28,18 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.APP_CATEGORIES
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
@@ -45,6 +47,7 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.withContext
 
@@ -58,6 +61,7 @@
     @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
     @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
     @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
+    @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
     private val inputManager: InputManager,
     stateRepository: ShortcutHelperStateRepository
 ) {
@@ -76,7 +80,7 @@
             if (it != null) {
                 toShortcutCategory(
                     it.keyCharacterMap,
-                    SYSTEM,
+                    System,
                     systemShortcutsSource.shortcutGroups(it.id),
                     keepIcons = true,
                 )
@@ -90,7 +94,7 @@
             if (it != null) {
                 toShortcutCategory(
                     it.keyCharacterMap,
-                    MULTI_TASKING,
+                    MultiTasking,
                     multitaskingShortcutsSource.shortcutGroups(it.id),
                     keepIcons = true,
                 )
@@ -104,7 +108,7 @@
             if (it != null) {
                 toShortcutCategory(
                     it.keyCharacterMap,
-                    APP_CATEGORIES,
+                    AppCategories,
                     appCategoriesShortcutsSource.shortcutGroups(it.id),
                     keepIcons = true,
                 )
@@ -118,7 +122,7 @@
             if (it != null) {
                 toShortcutCategory(
                     it.keyCharacterMap,
-                    IME,
+                    InputMethodEditor,
                     inputShortcutsSource.shortcutGroups(it.id),
                     keepIcons = false,
                 )
@@ -127,6 +131,26 @@
             }
         }
 
+    val currentAppShortcutsCategory: Flow<ShortcutCategory?> =
+        activeInputDevice.map {
+            if (it != null) {
+                val shortcutGroups = currentAppShortcutsSource.shortcutGroups(it.id)
+                val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
+                if (categoryType == null) {
+                    null
+                } else {
+                    toShortcutCategory(
+                        it.keyCharacterMap,
+                        categoryType,
+                        shortcutGroups,
+                        keepIcons = false
+                    )
+                }
+            } else {
+                null
+            }
+        }
+
     private fun toShortcutCategory(
         keyCharacterMap: KeyCharacterMap,
         type: ShortcutCategoryType,
@@ -150,6 +174,16 @@
         }
     }
 
+    private fun getCurrentAppShortcutCategoryType(
+        shortcutGroups: List<KeyboardShortcutGroup>
+    ): ShortcutCategoryType? {
+        return if (shortcutGroups.isEmpty()) {
+            null
+        } else {
+            CurrentApp(packageName = shortcutGroups[0].packageName.toString())
+        }
+    }
+
     private fun toShortcuts(
         keyCharacterMap: KeyCharacterMap,
         infoList: List<KeyboardShortcutInfo>,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index 0aced8c..cbe6fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -222,7 +222,7 @@
                 { context ->
                     context.getString(R.string.keyboard_key_forward_del)
                 },
-            KEYCODE_ESCAPE to { "Esc" },
+            KEYCODE_ESCAPE to { context -> context.getString(R.string.keyboard_key_esc) },
             KEYCODE_SYSRQ to { "SysRq" },
             KEYCODE_BREAK to { "Break" },
             KEYCODE_SCROLL_LOCK to { "Scroll Lock" },
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSource.kt
new file mode 100644
index 0000000..7e6ed19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSource.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.keyboard.shortcut.data.source
+
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager
+import android.view.WindowManager.KeyboardShortcutsReceiver
+import javax.inject.Inject
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+class CurrentAppShortcutsSource @Inject constructor(private val windowManager: WindowManager) :
+    KeyboardShortcutGroupsSource {
+    override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> =
+        suspendCancellableCoroutine { continuation ->
+            val shortcutsReceiver = KeyboardShortcutsReceiver {
+                continuation.resumeWith(Result.success(it ?: emptyList()))
+            }
+            windowManager.requestAppKeyboardShortcuts(shortcutsReceiver, deviceId)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
index aba4415..1b20986 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
@@ -59,7 +59,7 @@
     private suspend fun getImeShortcutGroup(deviceId: Int): List<KeyboardShortcutGroup> =
         suspendCancellableCoroutine { continuation ->
             val shortcutsReceiver = KeyboardShortcutsReceiver {
-                continuation.resumeWith(Result.success(it))
+                continuation.resumeWith(Result.success(it ?: emptyList()))
             }
             windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, deviceId)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index d41d21a..f215c74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -38,6 +38,7 @@
             categoriesRepository.multitaskingShortcutsCategory,
             categoriesRepository.imeShortcutsCategory,
             categoriesRepository.appCategoriesShortcutsCategory,
+            categoriesRepository.currentAppShortcutsCategory
         ) { shortcutCategories ->
             shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
similarity index 79%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
index c92234c..51631b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.keyboard.shortcut.qualifiers
 
 import javax.inject.Qualifier
 
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+@Qualifier annotation class CurrentAppShortcuts
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 63e167a..4eabefc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
-enum class ShortcutCategoryType {
-    SYSTEM,
-    MULTI_TASKING,
-    IME,
-    APP_CATEGORIES,
+sealed interface ShortcutCategoryType {
+    data object System : ShortcutCategoryType
+
+    data object MultiTasking : ShortcutCategoryType
+
+    data object InputMethodEditor : ShortcutCategoryType
+
+    data object AppCategories : ShortcutCategoryType
+
+    data class CurrentApp(val packageName: String) : ShortcutCategoryType
 }
 
 data class ShortcutCategory(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 3b037bc..9e9368d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.keyboard.shortcut.ui.composable
 
+import android.content.Context
+import android.content.pm.PackageManager.NameNotFoundException
 import android.graphics.drawable.Icon
+import android.util.Log
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Image
@@ -55,6 +58,7 @@
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.NavigationDrawerItemColors
 import androidx.compose.material3.NavigationDrawerItemDefaults
@@ -76,7 +80,6 @@
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
@@ -99,8 +102,10 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.CentralSurfaces
 
 @Composable
 fun ShortcutHelper(
@@ -210,9 +215,9 @@
                 verticalAlignment = Alignment.CenterVertically,
                 modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp)
             ) {
-                Icon(category.icon, contentDescription = null)
+                ShortcutCategoryIcon(category.icon)
                 Spacer(modifier = Modifier.width(16.dp))
-                Text(stringResource(category.labelResId))
+                Text(category.label(LocalContext.current))
                 Spacer(modifier = Modifier.weight(1f))
                 RotatingExpandCollapseIcon(isExpanded)
             }
@@ -221,23 +226,67 @@
     }
 }
 
-private val ShortcutCategory.icon: ImageVector
+private val ShortcutCategory.icon: IconSource
+    @Composable
     get() =
         when (type) {
-            ShortcutCategoryType.SYSTEM -> Icons.Default.Tv
-            ShortcutCategoryType.MULTI_TASKING -> Icons.Default.VerticalSplit
-            ShortcutCategoryType.IME -> Icons.Default.Keyboard
-            ShortcutCategoryType.APP_CATEGORIES -> Icons.Default.Apps
+            ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
+            ShortcutCategoryType.MultiTasking ->
+                IconSource(imageVector = Icons.Default.VerticalSplit)
+            ShortcutCategoryType.InputMethodEditor ->
+                IconSource(imageVector = Icons.Default.Keyboard)
+            ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
+            is ShortcutCategoryType.CurrentApp -> {
+                val context = LocalContext.current
+                val iconDrawable = context.packageManager.getApplicationIcon(type.packageName)
+                IconSource(painter = rememberDrawablePainter(drawable = iconDrawable))
+            }
         }
 
-private val ShortcutCategory.labelResId: Int
-    get() =
-        when (type) {
-            ShortcutCategoryType.SYSTEM -> R.string.shortcut_helper_category_system
-            ShortcutCategoryType.MULTI_TASKING -> R.string.shortcut_helper_category_multitasking
-            ShortcutCategoryType.IME -> R.string.shortcut_helper_category_input
-            ShortcutCategoryType.APP_CATEGORIES -> R.string.shortcut_helper_category_app_shortcuts
-        }
+@Composable
+fun ShortcutCategoryIcon(
+    source: IconSource,
+    contentDescription: String? = null,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current
+) {
+    if (source.imageVector != null) {
+        Icon(source.imageVector, contentDescription, modifier, tint)
+    } else if (source.painter != null) {
+        Image(source.painter, contentDescription, modifier)
+    }
+}
+
+private fun ShortcutCategory.label(context: Context): String =
+    when (type) {
+        ShortcutCategoryType.System -> context.getString(R.string.shortcut_helper_category_system)
+        ShortcutCategoryType.MultiTasking ->
+            context.getString(R.string.shortcut_helper_category_multitasking)
+        ShortcutCategoryType.InputMethodEditor ->
+            context.getString(R.string.shortcut_helper_category_input)
+        ShortcutCategoryType.AppCategories ->
+            context.getString(R.string.shortcut_helper_category_app_shortcuts)
+        is ShortcutCategoryType.CurrentApp -> getApplicationLabelForCurrentApp(type, context)
+    }
+
+private fun getApplicationLabelForCurrentApp(
+    type: ShortcutCategoryType.CurrentApp,
+    context: Context
+): String {
+    val packageManagerForUser = CentralSurfaces.getPackageManagerForUser(context, context.userId)
+    return try {
+        val currentAppInfo =
+            packageManagerForUser.getApplicationInfoAsUser(
+                type.packageName,
+                /* flags = */ 0,
+                context.userId
+            )
+        packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
+    } catch (e: NameNotFoundException) {
+        Log.wtf(ShortcutHelper.TAG, "Couldn't find app info by package name ${type.packageName}")
+        context.getString(R.string.shortcut_helper_category_current_app_shortcuts)
+    }
+}
 
 @Composable
 private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
@@ -525,8 +574,8 @@
     Column {
         categories.fastForEach {
             CategoryItemTwoPane(
-                label = stringResource(it.labelResId),
-                icon = it.icon,
+                label = it.label(LocalContext.current),
+                iconSource = it.icon,
                 selected = selectedCategory == it.type,
                 onClick = { onCategoryClicked(it) }
             )
@@ -537,7 +586,7 @@
 @Composable
 private fun CategoryItemTwoPane(
     label: String,
-    icon: ImageVector,
+    iconSource: IconSource,
     selected: Boolean,
     onClick: () -> Unit,
     colors: NavigationDrawerItemColors =
@@ -551,9 +600,9 @@
         color = colors.containerColor(selected).value,
     ) {
         Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
-            Icon(
+            ShortcutCategoryIcon(
                 modifier = Modifier.size(24.dp),
-                imageVector = icon,
+                source = iconSource,
                 contentDescription = null,
                 tint = colors.iconColor(selected).value
             )
@@ -649,4 +698,6 @@
     object Dimensions {
         val SinglePaneCategoryCornerRadius = 28.dp
     }
+
+    internal const val TAG = "ShortcutHelperUI"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
index c92234c..7fc0103 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.keyboard.shortcut.ui.model
 
-import javax.inject.Qualifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
 
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+data class IconSource(val imageVector: ImageVector? = null, val painter: Painter? = null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 33f9209..80cf4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -21,6 +21,7 @@
 import android.view.LayoutInflater
 import android.view.View
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.ComposeView
 import androidx.constraintlayout.widget.ConstraintSet
@@ -29,9 +30,9 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.transitions
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.KeyguardStatusViewController
@@ -115,7 +116,6 @@
 
     private var rootViewHandle: DisposableHandle? = null
     private var indicationAreaHandle: DisposableHandle? = null
-    private val sceneKey = SceneKey("root-view-scene-key")
 
     var keyguardStatusViewController: KeyguardStatusViewController? = null
         get() {
@@ -233,12 +233,10 @@
             setContent {
                 // STL is used solely to provide a SceneScope to enable us to invoke SceneScope
                 // composables.
-                SceneTransitionLayout(
-                    currentScene = sceneKey,
-                    onChangeScene = {},
-                    transitions = transitions {},
-                ) {
-                    scene(sceneKey) {
+                val currentScene = remember { SceneKey("root-view-scene-key") }
+                val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+                SceneTransitionLayout(state) {
+                    scene(currentScene) {
                         with(
                             LockscreenContent(
                                 viewModel = viewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index d41c0c5..893835a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -199,6 +199,7 @@
      * PRIMARY_BOUNCER.
      */
     private fun listenForAodToPrimaryBouncer() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForAodToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 84ca667..f3ca9df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -71,6 +71,8 @@
     }
 
     private fun listenForOccludedToPrimaryBouncer() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 41c3959..e2bb540 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -19,7 +19,6 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.content.Context
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
@@ -32,15 +31,12 @@
 import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
@@ -51,13 +47,10 @@
 constructor(
     private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
     @Application private val applicationScope: CoroutineScope,
-    private val context: Context,
-    private val shadeInteractor: ShadeInteractor,
-    private val clockInteractor: KeyguardClockInteractor,
+    shadeInteractor: ShadeInteractor,
     private val configurationInteractor: ConfigurationInteractor,
     private val fingerprintPropertyInteractor: FingerprintPropertyInteractor,
     private val smartspaceSection: SmartspaceSection,
-    private val clockSection: ClockSection,
 ) : CoreStartable {
     /** The current blueprint for the lockscreen. */
     val blueprint: StateFlow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint
@@ -70,8 +63,8 @@
 
     /** Current BlueprintId */
     val blueprintId =
-        shadeInteractor.shadeMode.map { shadeMode ->
-            val useSplitShade = shadeMode == ShadeMode.Split && !ComposeLockscreen.isEnabled
+        shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide ->
+            val useSplitShade = isShadeLayoutWide && !ComposeLockscreen.isEnabled
             when {
                 useSplitShade -> SplitShadeKeyguardBlueprint.ID
                 else -> DefaultKeyguardBlueprint.DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 142b1a0..ab432d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -45,6 +45,7 @@
 import kotlinx.coroutines.flow.stateIn
 
 private val TAG = KeyguardClockInteractor::class.simpleName
+
 /** Manages and encapsulates the clock components of the lockscreen root view. */
 @SysUISingleton
 class KeyguardClockInteractor
@@ -77,16 +78,16 @@
     val clockSize: StateFlow<ClockSize> =
         if (SceneContainerFlag.isEnabled) {
             combine(
-                    shadeInteractor.shadeMode,
+                    shadeInteractor.isShadeLayoutWide,
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     mediaCarouselInteractor.hasActiveMediaOrRecommendation,
                     keyguardInteractor.isDozing,
                     isOnAod,
-                ) { shadeMode, hasNotifs, hasMedia, isDozing, isOnAod ->
+                ) { isShadeLayoutWide, hasNotifs, hasMedia, isDozing, isOnAod ->
                     return@combine when {
                         keyguardClockRepository.shouldForceSmallClock && !isOnAod -> ClockSize.SMALL
-                        shadeMode == ShadeMode.Single && (hasNotifs || hasMedia) -> ClockSize.SMALL
-                        shadeMode == ShadeMode.Single -> ClockSize.LARGE
+                        !isShadeLayoutWide && (hasNotifs || hasMedia) -> ClockSize.SMALL
+                        !isShadeLayoutWide -> ClockSize.LARGE
                         hasMedia && !isDozing -> ClockSize.SMALL
                         else -> ClockSize.LARGE
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 1aac1c5..f4d8265 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -25,9 +25,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -35,6 +34,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.map
@@ -51,7 +51,6 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val dismissInteractor: KeyguardDismissInteractor,
     @Application private val applicationScope: CoroutineScope,
-    sceneInteractor: SceneInteractor,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -76,11 +75,10 @@
             )
 
     private val finishedTransitionToGone: Flow<Unit> =
-        if (SceneContainerFlag.isEnabled) {
-            sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {}
-        } else {
-            transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {}
-        }
+        transitionInteractor
+            .isFinishedIn(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+            .filter { it }
+            .map {}
 
     val executeDismissAction: Flow<() -> KeyguardDone> =
         merge(
@@ -90,12 +88,24 @@
             .sample(dismissAction)
             .filterNot { it is DismissAction.None }
             .map { it.onDismissAction }
+
     val resetDismissAction: Flow<Unit> =
-        transitionInteractor.finishedKeyguardTransitionStep
-            .filter { it.to != ALTERNATE_BOUNCER && it.to != PRIMARY_BOUNCER && it.to != GONE }
-            .sample(dismissAction)
-            .filterNot { it is DismissAction.None }
-            .map {} // map to Unit
+        combine(
+                transitionInteractor.isFinishedIn(
+                    scene = Scenes.Gone,
+                    stateWithoutSceneContainer = GONE
+                ),
+                transitionInteractor.isFinishedIn(
+                    scene = Scenes.Bouncer,
+                    stateWithoutSceneContainer = PRIMARY_BOUNCER
+                ),
+                transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER)
+            ) { isOnGone, isOnBouncer, isOnAltBouncer ->
+                !isOnGone && !isOnBouncer && !isOnAltBouncer
+            }
+            .filter { it }
+            .sampleFilter(dismissAction) { it !is DismissAction.None }
+            .map {}
 
     fun runDismissAnimationOnKeyguard(): Boolean {
         return willAnimateDismissActionOnLockscreen.value
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index 6a1b7cf..f0bf402 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -257,6 +258,10 @@
 
     /** Set an alarm for */
     private fun setResetCanIgnoreAuthAlarm() {
+        if (!KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
         val intent =
             Intent(DELAYED_KEYGUARD_ACTION).apply {
                 setPackage(context.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 0985e69..e1b333d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -82,6 +82,7 @@
     private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
         transitionInteractor.startedKeyguardTransitionStep
             .flatMapLatest { startedStep ->
+                SceneContainerFlag.assertInLegacyMode()
                 when (startedStep.from) {
                     KeyguardState.LOCKSCREEN -> {
                         fromLockscreenInteractor.surfaceBehindVisibility
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 9504352..24db3c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -61,6 +61,11 @@
      * The security screen prompt UI, containing PIN, Password, Pattern for the user to verify their
      * credentials.
      */
+    @Deprecated(
+        "This state won't exist anymore when scene container gets enabled. If you are " +
+            "writing prod code today, make sure to either use flag aware APIs in " +
+            "[KeyguardTransitionInteractor] or flag appropriately with [SceneContainerFlag]."
+    )
     PRIMARY_BOUNCER,
     /**
      * Device is actively displaying keyguard UI and is not in low-power mode. Device may be
@@ -72,12 +77,22 @@
      * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
      * or dream, as well as swipe down for the notifications and up for the bouncer.
      */
+    @Deprecated(
+        "This state won't exist anymore when scene container gets enabled. If you are " +
+            "writing prod code today, make sure to either use flag aware APIs in " +
+            "[KeyguardTransitionInteractor] or flag appropriately with [SceneContainerFlag]."
+    )
     GLANCEABLE_HUB,
     /**
      * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard is
      * being removed, but there are other cases where the user is swiping away keyguard, such as
      * with SWIPE security method or face unlock without bypass.
      */
+    @Deprecated(
+        "This state won't exist anymore when scene container gets enabled. If you are " +
+            "writing prod code today, make sure to either use flag aware APIs in " +
+            "[KeyguardTransitionInteractor] or flag appropriately with [SceneContainerFlag]."
+    )
     GONE,
     /**
      * Only used in scene framework. This means we are currently on any scene framework scene that
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 573b75e..73028c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
 import javax.inject.Inject
@@ -119,26 +118,25 @@
         combine(
                 isLargeClockVisible,
                 clockShouldBeCentered,
-                shadeInteractor.shadeMode,
+                shadeInteractor.isShadeLayoutWide,
                 currentClock,
-            ) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock ->
-                val shouldUseSplitShade = shadeMode == ShadeMode.Split
+            ) { isLargeClockVisible, clockShouldBeCentered, isShadeLayoutWide, currentClock ->
                 if (currentClock?.config?.useCustomClockScene == true) {
                     when {
-                        shouldUseSplitShade && clockShouldBeCentered ->
+                        isShadeLayoutWide && clockShouldBeCentered ->
                             ClockLayout.WEATHER_LARGE_CLOCK
-                        shouldUseSplitShade && isLargeClockVisible ->
+                        isShadeLayoutWide && isLargeClockVisible ->
                             ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK
-                        shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+                        isShadeLayoutWide -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
                         isLargeClockVisible -> ClockLayout.WEATHER_LARGE_CLOCK
                         else -> ClockLayout.SMALL_CLOCK
                     }
                 } else {
                     when {
-                        shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
-                        shouldUseSplitShade && isLargeClockVisible ->
+                        isShadeLayoutWide && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
+                        isShadeLayoutWide && isLargeClockVisible ->
                             ClockLayout.SPLIT_SHADE_LARGE_CLOCK
-                        shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+                        isShadeLayoutWide -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
                         isLargeClockVisible -> ClockLayout.LARGE_CLOCK
                         else -> ClockLayout.SMALL_CLOCK
                     }
@@ -164,7 +162,7 @@
     /** Calculates the top margin for the small clock. */
     fun getSmallClockTopMargin(): Int {
         val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard()
-        return if (shadeInteractor.shadeMode.value == ShadeMode.Split) {
+        return if (shadeInteractor.isShadeLayoutWide.value) {
             resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) -
                 if (ComposeLockscreen.isEnabled) statusBarHeight else 0
         } else {
@@ -176,7 +174,7 @@
     val smallClockTopMargin =
         combine(
             configurationInteractor.onAnyConfigurationChange,
-            shadeInteractor.shadeMode,
+            shadeInteractor.isShadeLayoutWide,
         ) { _, _ ->
             getSmallClockTopMargin()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 0fb29c2..7c46807 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -160,7 +160,7 @@
                     edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
                 ),
                 keyguardTransitionInteractor.isInTransition(
-                    edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Lockscreen),
+                    edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
                     edgeWithoutSceneContainer =
                         Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
                 ),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 8a29f96..3b337fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -56,21 +55,14 @@
     val isUdfpsVisible: Boolean
         get() = authController.isUdfpsSupported
 
-    val shouldUseSplitNotificationShade: StateFlow<Boolean> =
-        shadeInteractor.shadeMode
-            .map { it == ShadeMode.Split }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
+    val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
 
     val areNotificationsVisible: StateFlow<Boolean> =
         combine(
                 clockSize,
-                shouldUseSplitNotificationShade,
-            ) { clockSize, shouldUseSplitNotificationShade ->
-                clockSize == ClockSize.SMALL || shouldUseSplitNotificationShade
+                shadeInteractor.isShadeLayoutWide,
+            ) { clockSize, isShadeLayoutWide ->
+                clockSize == ClockSize.SMALL || isShadeLayoutWide
             }
             .stateIn(
                 scope = applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
index 754fb94..9ec15dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
-import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -33,10 +32,7 @@
 constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow
-            .setup(
-                duration = TO_GLANCEABLE_HUB_DURATION,
-                edge = Edge.create(PRIMARY_BOUNCER, Scenes.Communal)
-            )
+            .setup(duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.INVALID)
             .setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, GLANCEABLE_HUB))
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 48970f5..46c5c18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -845,8 +845,25 @@
         commonViewModels.addAll(viewModels)
 
         // Ensure we only show the needed UMOs in media carousel.
-        val viewSet = viewModels.toHashSet()
-        controllerByViewModel.filter { !viewSet.contains(it.key) }.forEach { onRemoved(it.key) }
+        val viewIds =
+            viewModels
+                .map { mediaCommonViewModel ->
+                    when (mediaCommonViewModel) {
+                        is MediaCommonViewModel.MediaControl ->
+                            mediaCommonViewModel.instanceId.toString()
+                        is MediaCommonViewModel.MediaRecommendations -> mediaCommonViewModel.key
+                    }
+                }
+                .toHashSet()
+        controllerByViewModel
+            .filter {
+                when (val viewModel = it.key) {
+                    is MediaCommonViewModel.MediaControl ->
+                        !viewIds.contains(viewModel.instanceId.toString())
+                    is MediaCommonViewModel.MediaRecommendations -> !viewIds.contains(viewModel.key)
+                }
+            }
+            .forEach { onRemoved(it.key) }
     }
 
     private suspend fun getMediaLockScreenSetting(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
index de300b2..82b4825 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
@@ -28,13 +28,25 @@
      *
      * @property hostPackage the package name of the app that is receiving the content of the media
      *   projection (aka which app the phone screen contents are being sent to).
+     * @property hostDeviceName the name of the other device that's receiving the content of the
+     *   media projection. Null if the media projection is going to this same device (e.g. another
+     *   app is recording the screen).
      */
-    sealed class Projecting(open val hostPackage: String) : MediaProjectionState {
+    sealed class Projecting(
+        open val hostPackage: String,
+        open val hostDeviceName: String?,
+    ) : MediaProjectionState {
         /** The entire screen is being projected. */
-        data class EntireScreen(override val hostPackage: String) : Projecting(hostPackage)
+        data class EntireScreen(
+            override val hostPackage: String,
+            override val hostDeviceName: String? = null,
+        ) : Projecting(hostPackage, hostDeviceName)
 
         /** Only a single task is being projected. */
-        data class SingleTask(override val hostPackage: String, val task: RunningTaskInfo) :
-            Projecting(hostPackage)
+        data class SingleTask(
+            override val hostPackage: String,
+            override val hostDeviceName: String?,
+            val task: RunningTaskInfo,
+        ) : Projecting(hostPackage, hostDeviceName)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 8a9adc7..071d8f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.mediaprojection.data.repository
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.hardware.display.DisplayManager
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
 import android.os.Handler
@@ -47,6 +48,7 @@
 @Inject
 constructor(
     private val mediaProjectionManager: MediaProjectionManager,
+    private val displayManager: DisplayManager,
     @Main private val handler: Handler,
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -110,14 +112,21 @@
         }
 
         val hostPackage = info.packageName
+        val hostDeviceName =
+            withContext(backgroundDispatcher) {
+                // If the projection is to a different device, then the session's display ID should
+                // identify the display associated with that different device.
+                displayManager.getDisplay(session.virtualDisplayId)?.name
+            }
+
         if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
-            return MediaProjectionState.Projecting.EntireScreen(hostPackage)
+            return MediaProjectionState.Projecting.EntireScreen(hostPackage, hostDeviceName)
         }
         val matchingTask =
             tasksRepository.findRunningTaskFromWindowContainerToken(
                 checkNotNull(session.tokenToRecord)
-            ) ?: return MediaProjectionState.Projecting.EntireScreen(hostPackage)
-        return MediaProjectionState.Projecting.SingleTask(hostPackage, matchingTask)
+            ) ?: return MediaProjectionState.Projecting.EntireScreen(hostPackage, hostDeviceName)
+        return MediaProjectionState.Projecting.SingleTask(hostPackage, hostDeviceName, matchingTask)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 10c8e53..cf1dca3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import android.content.res.Resources;
+
 import com.android.systemui.statusbar.policy.CallbackController;
 
 public interface ReduceBrightColorsController extends
@@ -27,6 +29,14 @@
     /** Sets the activation state of Reduce Bright Colors */
     void setReduceBrightColorsActivated(boolean activated);
 
+    /** Sets whether Reduce Bright Colors is enabled */
+    void setReduceBrightColorsFeatureAvailable(boolean enabled);
+
+    /** Gets whether Reduce Bright Colors is enabled */
+    boolean isReduceBrightColorsFeatureAvailable();
+
+    /** Gets whether Reduce Bright Colors is being transitioned to Even Dimmer */
+    boolean isInUpgradeMode(Resources resources);
     /**
      * Listener invoked whenever the Reduce Bright Colors settings are changed.
      */
@@ -38,5 +48,12 @@
          */
         default void onActivated(boolean activated) {
         }
+        /**
+         * Listener invoked when the feature enabled state changes.
+         *
+         * @param enabled {@code true} if Reduce Bright Colors feature is enabled.
+         */
+        default void onFeatureEnabledChanged(boolean enabled) {
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index 846d63f..d68b22b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -19,6 +19,7 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
 import android.net.Uri;
@@ -28,6 +29,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.server.display.feature.flags.Flags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.UserTracker;
@@ -47,6 +49,7 @@
     private final ContentObserver mContentObserver;
     private final SecureSettings mSecureSettings;
     private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
+    private boolean mAvailable = true;
 
     @Inject
     public ReduceBrightColorsControllerImpl(UserTracker userTracker,
@@ -75,6 +78,7 @@
         mCurrentUserTrackerCallback = new UserTracker.Callback() {
             @Override
             public void onUserChanged(int newUser, Context userContext) {
+                mAvailable = true;
                 synchronized (mListeners) {
                     if (mListeners.size() > 0) {
                         mSecureSettings.unregisterContentObserverSync(mContentObserver);
@@ -121,10 +125,35 @@
         mManager.setReduceBrightColorsActivated(activated);
     }
 
+    @Override
+    public void setReduceBrightColorsFeatureAvailable(boolean enabled) {
+        mAvailable = enabled;
+        dispatchOnEnabledChanged(enabled);
+        mAvailable = true;
+    }
+
+    @Override
+    public boolean isReduceBrightColorsFeatureAvailable() {
+        return mAvailable;
+    }
+
+    @Override
+    public boolean isInUpgradeMode(Resources resources) {
+        return Flags.evenDimmer() && resources.getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+    }
+
     private void dispatchOnActivated(boolean activated) {
         ArrayList<Listener> copy = new ArrayList<>(mListeners);
         for (Listener l : copy) {
             l.onActivated(activated);
         }
     }
+
+    private void dispatchOnEnabledChanged(boolean enabled) {
+        ArrayList<Listener> copy = new ArrayList<>(mListeners);
+        for (Listener l : copy) {
+            l.onFeatureEnabledChanged(enabled);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6092348..2287f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -19,6 +19,7 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 
+import com.android.systemui.Flags;
 import com.android.systemui.statusbar.policy.Listenable;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.settings.SettingsProxy;
@@ -74,10 +75,20 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserverSync(
-                    mSettingsProxy.getUriFor(mSettingName), false, this);
+            if (Flags.qsRegisterSettingObserverOnBgThread()) {
+                mSettingsProxy.registerContentObserverAsync(
+                        mSettingsProxy.getUriFor(mSettingName), false, this,
+                        () -> mObservedValue = getValueFromProvider());
+            } else {
+                mSettingsProxy.registerContentObserverSync(
+                        mSettingsProxy.getUriFor(mSettingName), false, this);
+            }
         } else {
-            mSettingsProxy.unregisterContentObserverSync(this);
+            if (Flags.qsRegisterSettingObserverOnBgThread()) {
+                mSettingsProxy.unregisterContentObserverAsync(this);
+            } else {
+                mSettingsProxy.unregisterContentObserverSync(this);
+            }
             mObservedValue = mDefaultValue;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index a45d6f6..89f85ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -32,6 +32,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.server.display.feature.flags.Flags;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.qs.QSTile;
@@ -116,6 +117,10 @@
         final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
         possibleTiles.remove("cell");
         possibleTiles.remove("wifi");
+        if (Flags.evenDimmer() && mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled)) {
+            possibleTiles.remove("reduce_brightness");
+        }
 
         for (String spec : possibleTiles) {
             // Only add current and stock tiles that can be created from QSFactoryImpl.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index ff20707..78f4b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -19,18 +19,17 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
 import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
 import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
 import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
 import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
 import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
-import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
 import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
 import com.android.systemui.qs.panels.ui.compose.GridLayout
@@ -53,7 +52,10 @@
 
 @Module
 interface PanelsModule {
-    @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
+    @Binds
+    fun bindDefaultLargeTilesSpecsRepository(
+        impl: DefaultLargeTilesRepositoryImpl
+    ): DefaultLargeTilesRepository
 
     @Binds
     fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
@@ -87,16 +89,9 @@
     companion object {
         @Provides
         @SysUISingleton
-        @GridConsistencyLog
-        fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer {
-            return factory.create("GridConsistencyLog", 50)
-        }
-
-        @Provides
-        @SysUISingleton
-        @IconLabelVisibilityLog
-        fun providesIconTileLabelVisibilityLog(factory: LogBufferFactory): LogBuffer {
-            return factory.create("IconLabelVisibilityLog", 50)
+        @PanelsLog
+        fun providesPanelsLog(factory: LogBufferFactory): LogBuffer {
+            return factory.create("PanelsLog", 50)
         }
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepository.kt
new file mode 100644
index 0000000..9a1671d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/** Repository for the default set of [TileSpec] that should be displayed as large tiles. */
+interface DefaultLargeTilesRepository {
+    val defaultLargeTiles: Set<TileSpec>
+}
+
+@SysUISingleton
+class DefaultLargeTilesRepositoryImpl @Inject constructor() : DefaultLargeTilesRepository {
+    override val defaultLargeTiles =
+        setOf(
+            TileSpec.create("internet"),
+            TileSpec.create("bt"),
+            TileSpec.create("dnd"),
+            TileSpec.create("cast"),
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
deleted file mode 100644
index 095bdf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-
-/** Repository for checking if a tile should be displayed as an icon. */
-interface IconTilesRepository {
-    fun isIconTile(spec: TileSpec): Boolean
-}
-
-@SysUISingleton
-class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
-
-    override fun isIconTile(spec: TileSpec): Boolean {
-        return !LARGE_TILES.contains(spec)
-    }
-
-    companion object {
-        private val LARGE_TILES =
-            setOf(
-                TileSpec.create("internet"),
-                TileSpec.create("bt"),
-                TileSpec.create("dnd"),
-                TileSpec.create("cast"),
-            )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index f3e5b8f..971598d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -20,6 +20,7 @@
 import android.content.SharedPreferences
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -40,6 +41,7 @@
 constructor(
     private val userFileManager: UserFileManager,
     private val userRepository: UserRepository,
+    private val defaultLargeTilesRepository: DefaultLargeTilesRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
     /** Whether to show the labels on icon tiles for the current user. */
@@ -51,6 +53,23 @@
             }
             .flowOn(backgroundDispatcher)
 
+    /** Set of [TileSpec] to display as large tiles for the current user. */
+    val largeTilesSpecs: Flow<Set<TileSpec>> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                val prefs = getSharedPrefs(userInfo.id)
+                prefs.observe().emitOnStart().map {
+                    prefs
+                        .getStringSet(
+                            LARGE_TILES_SPECS_KEY,
+                            defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet()
+                        )
+                        ?.map { TileSpec.create(it) }
+                        ?.toSet() ?: defaultLargeTilesRepository.defaultLargeTiles
+                }
+            }
+            .flowOn(backgroundDispatcher)
+
     /** Sets for the current user whether to show the labels on icon tiles. */
     fun setShowLabels(showLabels: Boolean) {
         with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
@@ -58,6 +77,13 @@
         }
     }
 
+    /** Sets for the current user the set of [TileSpec] to display as large tiles. */
+    fun setLargeTilesSpecs(specs: Set<TileSpec>) {
+        with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
+            edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+        }
+    }
+
     private fun getSharedPrefs(userId: Int): SharedPreferences {
         return userFileManager.getSharedPreferences(
             FILE_NAME,
@@ -68,6 +94,7 @@
 
     companion object {
         private const val ICON_LABELS_KEY = "show_icon_labels"
+        private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
         const val FILE_NAME = "quick_settings_prefs"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
index ec9d151..86a29f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.panels.data.repository
 
 import android.content.res.Resources
+import com.android.server.display.feature.flags.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -32,10 +33,15 @@
     /**
      * List of stock platform tiles. All of the specs will be of type [TileSpec.PlatformTileSpec].
      */
+    val shouldRemoveRbcTile: Boolean =
+        Flags.evenDimmer() &&
+            resources.getBoolean(com.android.internal.R.bool.config_evenDimmerEnabled)
+
     val stockTiles =
         resources
             .getString(R.string.quick_settings_tiles_stock)
             .split(",")
+            .filterNot { shouldRemoveRbcTile && it.equals("reduce_brightness") }
             .map(TileSpec::create)
             .filterNot { it is TileSpec.Invalid }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
index 7732092..a2e7ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -39,7 +39,7 @@
     private val consistencyInteractors:
         Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
     private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
-    @GridConsistencyLog private val logBuffer: LogBuffer,
+    @PanelsLog private val logBuffer: LogBuffer,
     @Application private val applicationScope: CoroutineScope,
 ) {
     fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
index 6a899b0..fe40127 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
+import com.android.systemui.qs.panels.shared.model.PanelsLog
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -33,7 +33,7 @@
 @Inject
 constructor(
     private val preferencesInteractor: QSPreferencesInteractor,
-    @IconLabelVisibilityLog private val logBuffer: LogBuffer,
+    @PanelsLog private val logBuffer: LogBuffer,
     @Application scope: CoroutineScope,
 ) {
     val showLabels: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 524ea8b..b18358c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -17,12 +17,54 @@
 package com.android.systemui.qs.panels.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
 
-/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
+/** Interactor for retrieving the list of [TileSpec] to be displayed as icons and resizing icons. */
 @SysUISingleton
-class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) {
-    fun isIconTile(spec: TileSpec): Boolean = repo.isIconTile(spec)
+class IconTilesInteractor
+@Inject
+constructor(
+    repo: DefaultLargeTilesRepository,
+    private val preferencesInteractor: QSPreferencesInteractor,
+    @PanelsLog private val logBuffer: LogBuffer,
+    @Application private val applicationScope: CoroutineScope
+) {
+
+    private val largeTilesSpecs =
+        preferencesInteractor.largeTilesSpecs
+            .onEach { logChange(it) }
+            .stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
+
+    fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
+
+    fun resize(spec: TileSpec, toIcon: Boolean) {
+        if (toIcon) {
+            preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
+        } else {
+            preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
+        }
+    }
+
+    private fun logChange(specs: Set<TileSpec>) {
+        logBuffer.log(
+            LOG_BUFFER_LARGE_TILES_SPECS_CHANGE_TAG,
+            LogLevel.DEBUG,
+            { str1 = specs.toString() },
+            { "Large tiles change: $str1" }
+        )
+    }
+
+    private companion object {
+        const val LOG_BUFFER_LARGE_TILES_SPECS_CHANGE_TAG = "LargeTilesSpecsChange"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
index 811be80..854e23f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
@@ -18,14 +18,20 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
 @SysUISingleton
 class QSPreferencesInteractor @Inject constructor(private val repo: QSPreferencesRepository) {
     val showLabels: Flow<Boolean> = repo.showLabels
+    val largeTilesSpecs: Flow<Set<TileSpec>> = repo.largeTilesSpecs
 
     fun setShowLabels(showLabels: Boolean) {
         repo.setShowLabels(showLabels)
     }
+
+    fun setLargeTilesSpecs(specs: Set<TileSpec>) {
+        repo.setLargeTilesSpecs(specs)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
deleted file mode 100644
index 884cde3..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class GridConsistencyLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/PanelsLog.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/PanelsLog.kt
index c92234c..50cb386 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/PanelsLog.kt
@@ -18,7 +18,4 @@
 
 import javax.inject.Qualifier
 
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class PanelsLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 007ec3a..295a998 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -149,11 +149,13 @@
 fun Modifier.dragAndDropTileSource(
     tileSpec: TileSpec,
     onTap: (TileSpec) -> Unit,
+    onDoubleTap: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState
 ): Modifier {
     return dragAndDropSource {
         detectTapGestures(
             onTap = { onTap(tileSpec) },
+            onDoubleTap = { onDoubleTap(tileSpec) },
             onLongPress = {
                 dragAndDropState.onStarted(tileSpec)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index ea97f0d..ada774d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -22,6 +22,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -76,14 +77,18 @@
         onRemoveTile: (TileSpec) -> Unit,
     ) {
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val isIcon: (TileSpec) -> Boolean by rememberUpdatedState { tileSpec ->
+            iconTilesViewModel.isIconTile(tileSpec)
+        }
 
         DefaultEditTileGrid(
             tiles = tiles,
-            isIconOnly = iconTilesViewModel::isIconTile,
+            isIconOnly = isIcon,
             columns = GridCells.Fixed(columns),
             modifier = modifier,
             onAddTile = onAddTile,
             onRemoveTile = onRemoveTile,
+            onResize = iconTilesViewModel::resize,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 092ad44..8ca91d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -122,6 +122,9 @@
         val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
             onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
         }
+        val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
+            viewModel.resize(tileSpec, !viewModel.isIconTile(tileSpec))
+        }
         val largeTileHeight = tileHeight()
         val iconTileHeight = tileHeight(showLabels)
         val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
@@ -161,6 +164,7 @@
                 tilePadding = tilePadding,
                 onAdd = onAddTile,
                 onRemove = onRemoveTile,
+                onDoubleTap = onDoubleTap,
                 isIconOnly = viewModel::isIconTile,
                 columns = columns,
                 showLabels = showLabels,
@@ -173,6 +177,7 @@
                 tilePadding = tilePadding,
                 addTileToEnd = addTileToEnd,
                 onRemove = onRemoveTile,
+                onDoubleTap = onDoubleTap,
                 isIconOnly = viewModel::isIconTile,
                 showLabels = showLabels,
                 columns = columns,
@@ -203,6 +208,7 @@
         tilePadding: Dp,
         onAdd: (TileSpec, Int) -> Unit,
         onRemove: (TileSpec) -> Unit,
+        onDoubleTap: (TileSpec) -> Unit,
         isIconOnly: (TileSpec) -> Boolean,
         showLabels: Boolean,
         columns: Int,
@@ -224,6 +230,7 @@
                     tiles = largeTiles,
                     clickAction = ClickAction.REMOVE,
                     onClick = onRemove,
+                    onDoubleTap = onDoubleTap,
                     isIconOnly = { false },
                     dragAndDropState = dragAndDropState,
                     acceptDrops = { !isIconOnly(it) },
@@ -244,6 +251,7 @@
                     tiles = smallTiles,
                     clickAction = ClickAction.REMOVE,
                     onClick = onRemove,
+                    onDoubleTap = onDoubleTap,
                     isIconOnly = { true },
                     showLabels = showLabels,
                     dragAndDropState = dragAndDropState,
@@ -263,6 +271,7 @@
         tilePadding: Dp,
         addTileToEnd: (TileSpec) -> Unit,
         onRemove: (TileSpec) -> Unit,
+        onDoubleTap: (TileSpec) -> Unit,
         isIconOnly: (TileSpec) -> Boolean,
         showLabels: Boolean,
         columns: Int,
@@ -296,6 +305,7 @@
                     largeTiles,
                     ClickAction.ADD,
                     addTileToEnd,
+                    onDoubleTap,
                     isIconOnly,
                     dragAndDropState,
                     acceptDrops = { true },
@@ -308,6 +318,7 @@
                     smallTiles,
                     ClickAction.ADD,
                     addTileToEnd,
+                    onDoubleTap,
                     isIconOnly,
                     showLabels = showLabels,
                     dragAndDropState = dragAndDropState,
@@ -321,6 +332,7 @@
                     tilesCustom,
                     ClickAction.ADD,
                     addTileToEnd,
+                    onDoubleTap,
                     isIconOnly,
                     showLabels = showLabels,
                     dragAndDropState = dragAndDropState,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index 4a90102..770d4412 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -104,6 +104,7 @@
             modifier = modifier,
             onAddTile = onAddTile,
             onRemoveTile = onRemoveTile,
+            onResize = iconTilesViewModel::resize,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 127ef84..3fdd7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,7 +22,6 @@
 import android.service.quicksettings.Tile.STATE_ACTIVE
 import android.service.quicksettings.Tile.STATE_INACTIVE
 import android.text.TextUtils
-import android.util.Log
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
 import androidx.compose.animation.graphics.res.animatedVectorResource
@@ -274,6 +273,7 @@
     modifier: Modifier,
     onAddTile: (TileSpec, Int) -> Unit,
     onRemoveTile: (TileSpec) -> Unit,
+    onResize: (TileSpec, Boolean) -> Unit,
 ) {
     val currentListState = rememberEditListState(tiles)
     val dragAndDropState = rememberDragAndDropState(currentListState)
@@ -293,6 +293,9 @@
     val onDropRemove: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
         onRemoveTile(tileSpec)
     }
+    val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
+        onResize(tileSpec, !isIconOnly(tileSpec))
+    }
 
     TileLazyGrid(
         modifier = modifier.dragAndDropTileList(dragAndDropState, { true }, onDropAdd),
@@ -305,6 +308,7 @@
             currentTiles,
             ClickAction.REMOVE,
             onRemoveTile,
+            onDoubleTap,
             isIconOnly,
             indicatePosition = true,
             dragAndDropState = dragAndDropState,
@@ -318,6 +322,7 @@
             otherTilesStock,
             ClickAction.ADD,
             addTileToEnd,
+            onDoubleTap,
             isIconOnly,
             dragAndDropState = dragAndDropState,
             acceptDrops = { true },
@@ -332,6 +337,7 @@
             otherTilesCustom,
             ClickAction.ADD,
             addTileToEnd,
+            onDoubleTap,
             isIconOnly,
             dragAndDropState = dragAndDropState,
             acceptDrops = { true },
@@ -344,6 +350,7 @@
     tiles: List<EditTileViewModel>,
     clickAction: ClickAction,
     onClick: (TileSpec) -> Unit,
+    onDoubleTap: (TileSpec) -> Unit,
     isIconOnly: (TileSpec) -> Boolean,
     dragAndDropState: DragAndDropState,
     acceptDrops: (TileSpec) -> Boolean,
@@ -390,6 +397,7 @@
                         .dragAndDropTileSource(
                             viewModel.tileSpec,
                             onClick,
+                            onDoubleTap,
                             dragAndDropState,
                         )
             )
@@ -456,7 +464,6 @@
     animateToEnd: Boolean = false,
     modifier: Modifier = Modifier,
 ) {
-    Log.d("Fabian", "Recomposing tile icon")
     val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
     val context = LocalContext.current
     val loadedDrawable =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 117c85c..8d2d74a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -23,10 +23,14 @@
 
 interface IconTilesViewModel {
     fun isIconTile(spec: TileSpec): Boolean
+
+    fun resize(spec: TileSpec, toIcon: Boolean)
 }
 
 @SysUISingleton
 class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
     IconTilesViewModel {
     override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
+
+    override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
index 9c1b857..4d823ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
@@ -38,10 +38,11 @@
 @Inject
 constructor(
     controller: ReduceBrightColorsController,
-    @Named(RBC_AVAILABLE) private val available: Boolean,
+    @Named(RBC_AVAILABLE) private var available: Boolean,
 ) :
     CallbackControllerAutoAddable<
-        ReduceBrightColorsController.Listener, ReduceBrightColorsController
+        ReduceBrightColorsController.Listener,
+        ReduceBrightColorsController
     >(controller) {
 
     override val spec: TileSpec
@@ -50,10 +51,16 @@
     override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
         return object : ReduceBrightColorsController.Listener {
             override fun onActivated(activated: Boolean) {
-                if (activated) {
+                if (activated && available) {
                     sendAdd()
                 }
             }
+
+            override fun onFeatureEnabledChanged(enabled: Boolean) {
+                if (!enabled) {
+                    available = false
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 44c846b..787fd1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -386,6 +386,7 @@
             // The launch animation of a long-press effect did not reset the long-press effect so
             // we must do it here
             resetLongPressEffectProperties()
+            longPressEffect.resetState()
         }
         val actualHeight =
             if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
@@ -771,11 +772,14 @@
         lastIconTint = icon.getColor(state)
 
         // Long-press effects
+        longPressEffect?.qsTile?.state?.handlesLongClick = state.handlesLongClick
         if (
             state.handlesLongClick &&
                 longPressEffect?.initializeEffect(longPressEffectDuration) == true
         ) {
             showRippleEffect = false
+            longPressEffect.qsTile?.state?.state = lastState // Store the tile's state
+            longPressEffect.resetState()
             initializeLongPressProperties(measuredHeight, measuredWidth)
         } else {
             // Long-press effects might have been enabled before but the new state does not
@@ -906,6 +910,7 @@
     }
 
     override fun onActivityLaunchAnimationEnd() {
+        longPressEffect?.resetState()
         if (longPressEffect != null && !haveLongPressPropertiesBeenReset) {
             resetLongPressEffectProperties()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 3472352..af5b311 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -48,12 +48,13 @@
 
 /** Quick settings tile: Reduce Bright Colors **/
 public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
-        implements ReduceBrightColorsController.Listener{
+        implements ReduceBrightColorsController.Listener {
 
     public static final String TILE_SPEC = "reduce_brightness";
-    private final boolean mIsAvailable;
+    private boolean mIsAvailable;
     private final ReduceBrightColorsController mReduceBrightColorsController;
     private boolean mIsListening;
+    private final boolean mInUpgradeMode;
 
     @Inject
     public ReduceBrightColorsTile(
@@ -73,9 +74,11 @@
                 statusBarStateController, activityStarter, qsLogger);
         mReduceBrightColorsController = reduceBrightColorsController;
         mReduceBrightColorsController.observe(getLifecycle(), this);
-        mIsAvailable = isAvailable;
 
+        mInUpgradeMode = reduceBrightColorsController.isInUpgradeMode(mContext.getResources());
+        mIsAvailable = isAvailable || mInUpgradeMode;
     }
+
     @Override
     public boolean isAvailable() {
         return mIsAvailable;
@@ -93,12 +96,28 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return new Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS);
+        return goToEvenDimmer() ? new Intent(Settings.ACTION_DISPLAY_SETTINGS) : new Intent(
+                Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS);
+    }
+
+    private boolean goToEvenDimmer() {
+        if (mInUpgradeMode) {
+            mHost.removeTile(getTileSpec());
+            mIsAvailable = false;
+            return true;
+        }
+        return false;
     }
 
     @Override
     protected void handleClick(@Nullable Expandable expandable) {
-        mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value);
+
+        if (goToEvenDimmer()) {
+            mActivityStarter.postStartActivityDismissingKeyguard(
+                    new Intent(Settings.ACTION_DISPLAY_SETTINGS), 0);
+        } else {
+            mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value);
+        }
     }
 
     @Override
@@ -127,4 +146,9 @@
     public void onActivated(boolean activated) {
         refreshState();
     }
+
+    @Override
+    public void onFeatureEnabledChanged(boolean enabled) {
+        refreshState();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
index 98fd561..00b1e41 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
@@ -23,13 +23,13 @@
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.util.kotlin.isAvailable
 import com.android.systemui.util.kotlin.isEnabled
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
@@ -52,5 +52,7 @@
             .map { ReduceBrightColorsTileModel(it) }
             .flowOn(bgCoroutineContext)
     }
-    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable)
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        reduceBrightColorsController.isAvailable()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
index 14dbe0e..ed5e4fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
 
 import android.content.Intent
+import android.content.res.Resources
 import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.ReduceBrightColorsController
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
@@ -30,19 +32,40 @@
 class ReduceBrightColorsTileUserActionInteractor
 @Inject
 constructor(
+    @Main private val resources: Resources,
     private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
     private val reduceBrightColorsController: ReduceBrightColorsController,
 ) : QSTileUserActionInteractor<ReduceBrightColorsTileModel> {
 
+    val isInUpgradeMode: Boolean = reduceBrightColorsController.isInUpgradeMode(resources)
+
     override suspend fun handleInput(input: QSTileInput<ReduceBrightColorsTileModel>): Unit =
         with(input) {
             when (action) {
                 is QSTileUserAction.Click -> {
+                    if (isInUpgradeMode) {
+                        reduceBrightColorsController.setReduceBrightColorsFeatureAvailable(false)
+                        qsTileIntentUserActionHandler.handle(
+                            action.expandable,
+                            Intent(Settings.ACTION_DISPLAY_SETTINGS)
+                        )
+                        // TODO(b/349458355): show dialog
+                        return@with
+                    }
                     reduceBrightColorsController.setReduceBrightColorsActivated(
                         !input.data.isEnabled
                     )
                 }
                 is QSTileUserAction.LongClick -> {
+                    if (isInUpgradeMode) {
+                        reduceBrightColorsController.setReduceBrightColorsFeatureAvailable(false)
+                        qsTileIntentUserActionHandler.handle(
+                            action.expandable,
+                            Intent(Settings.ACTION_DISPLAY_SETTINGS)
+                        )
+                        // TODO(b/349458355): show dialog
+                        return@with
+                    }
                     qsTileIntentUserActionHandler.handle(
                         action.expandable,
                         Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 95ee2e0..0cbf8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
 import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
+import static com.android.systemui.Flags.screenshotSaveImageExporter;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -230,13 +231,6 @@
     private String mPackageName = "";
     private final BroadcastReceiver mCopyBroadcastReceiver;
 
-    // When false, the screenshot is taken without showing the ui. Note that this only applies to
-    // external displays, as on the default one the UI should **always** be shown.
-    // This is needed in case of screenshot during display mirroring, as adding another window to
-    // the external display makes mirroring stop.
-    // When there is a way to distinguish between displays that are mirroring or extending, this
-    // can be removed and we can directly show the ui only in the extended case.
-    private final Boolean mShowUIOnExternalDisplay;
     /** Tracks config changes that require re-creating UI */
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_ORIENTATION
@@ -272,8 +266,7 @@
             MessageContainerController messageContainerController,
             Provider<ScreenshotSoundController> screenshotSoundController,
             AnnouncementResolver announcementResolver,
-            @Assisted Display display,
-            @Assisted boolean showUIOnExternalDisplay
+            @Assisted Display display
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsControllerFactory.create(
@@ -347,7 +340,6 @@
         mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
                         ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
                 Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
-        mShowUIOnExternalDisplay = showUIOnExternalDisplay;
     }
 
     @Override
@@ -381,7 +373,7 @@
             Log.w(TAG, "User setup not complete, displaying toast only");
             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
             // and sharing shouldn't be exposed to the user.
-            saveScreenshotAndToast(screenshot.getUserHandle(), finisher);
+            saveScreenshotAndToast(screenshot, finisher);
             return;
         }
 
@@ -397,17 +389,15 @@
 
         prepareViewForNewScreenshot(screenshot, oldPackageName);
 
-        if (!shouldShowUi()) {
-            saveScreenshotInWorkerThread(
-                    screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
-                    (ignored) -> {
-                    });
-            return;
-        }
-
         final UUID requestId;
         requestId = mActionsController.setCurrentScreenshot(screenshot);
-        saveScreenshotInBackground(screenshot, requestId, finisher);
+        saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
+            if (result.uri != null) {
+                ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
+                        result.uri, screenshot.getUserOrDefault(), result.timestamp);
+                mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
+            }
+        });
 
         if (screenshot.getTaskId() >= 0) {
             mAssistContentRequester.requestAssistContent(
@@ -453,10 +443,6 @@
                 (v, insets) -> WindowInsets.CONSUMED);
     }
 
-    private boolean shouldShowUi() {
-        return mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
-    }
-
     void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
         withWindowAttached(() -> {
             if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
@@ -724,29 +710,40 @@
      * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
      * failure).
      */
-    private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
+    private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
         playCameraSoundIfNeeded();
 
-        saveScreenshotInWorkerThread(
-                owner,
-                /* onComplete */ finisher,
-                /* actionsReadyListener */ imageData -> {
-                    if (DEBUG_CALLBACK) {
-                        Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri);
-                    }
-                    finisher.accept(imageData.uri);
-                    if (imageData.uri == null) {
-                        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
-                        mNotificationsController.notifyScreenshotError(
-                                R.string.screenshot_failed_to_save_text);
-                    } else {
-                        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
-                        mScreenshotHandler.post(() -> Toast.makeText(mContext,
-                                R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
-                    }
-                },
-                null);
+        if (screenshotSaveImageExporter()) {
+            saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
+                if (result.uri != null) {
+                    mScreenshotHandler.post(() -> Toast.makeText(mContext,
+                            R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+                }
+            });
+        } else {
+            saveScreenshotInWorkerThread(
+                    screenshot.getUserHandle(),
+                    /* onComplete */ finisher,
+                    /* actionsReadyListener */ imageData -> {
+                        if (DEBUG_CALLBACK) {
+                            Log.d(TAG,
+                                    "returning URI to finisher (Consumer<URI>): " + imageData.uri);
+                        }
+                        finisher.accept(imageData.uri);
+                        if (imageData.uri == null) {
+                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0,
+                                    mPackageName);
+                            mNotificationsController.notifyScreenshotError(
+                                    R.string.screenshot_failed_to_save_text);
+                        } else {
+                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+                            mScreenshotHandler.post(() -> Toast.makeText(mContext,
+                                    R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+                        }
+                    },
+                    null);
+        }
     }
 
     /**
@@ -819,8 +816,8 @@
         mScreenshotHandler.cancelTimeout();
     }
 
-    private void saveScreenshotInBackground(
-            ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
+    private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId,
+            Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) {
         ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
                 requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(),
                 mDisplay.getDisplayId());
@@ -829,10 +826,7 @@
                 ImageExporter.Result result = future.get();
                 Log.d(TAG, "Saved screenshot: " + result);
                 logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
-                if (result.uri != null) {
-                    mActionsController.setCompletedScreenshot(requestId, new ScreenshotSavedResult(
-                            result.uri, screenshot.getUserOrDefault(), result.timestamp));
-                }
+                onResult.accept(result);
                 if (DEBUG_CALLBACK) {
                     Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
                             + "finisher.accept(\"" + result.uri + "\"");
@@ -1028,9 +1022,7 @@
          * Creates an instance of the controller for that specific display.
          *
          * @param display                 display to capture
-         * @param showUIOnExternalDisplay Whether the UI should be shown if this is an external
-         *                                display.
          */
-        ScreenshotController create(Display display, boolean showUIOnExternalDisplay);
+        ScreenshotController create(Display display);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 2699657..07f6e85 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -219,7 +219,7 @@
     }
 
     private fun getScreenshotController(display: Display): ScreenshotController {
-        val controller = screenshotController ?: screenshotControllerFactory.create(display, false)
+        val controller = screenshotController ?: screenshotControllerFactory.create(display)
         screenshotController = controller
         return controller
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 8c833ec..bd9e295 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -33,6 +33,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.HardwareRenderer;
 import android.graphics.RecordingCanvas;
@@ -267,6 +268,8 @@
     }
 
     private boolean canAppStartThroughLauncher(String packageName) {
+        // Use Intent.resolveActivity API to check if the intent resolves as that is what Android
+        // uses internally when apps use Context.startActivity.
         return getMainLauncherIntentForPackage(packageName).resolveActivity(mPackageManager)
                 != null;
     }
@@ -366,10 +369,19 @@
         return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString();
     }
 
-    private Intent getMainLauncherIntentForPackage(String packageName) {
-        return new Intent(ACTION_MAIN)
-                .addCategory(CATEGORY_LAUNCHER)
-                .setPackage(packageName);
+    private Intent getMainLauncherIntentForPackage(String pkgName) {
+        Intent intent = new Intent(ACTION_MAIN).addCategory(CATEGORY_LAUNCHER).setPackage(pkgName);
+
+        // Not all apps use DEFAULT_CATEGORY for their main launcher activity so the exact component
+        // needs to be queried and set on the Intent in order for note-taking apps to be able to
+        // start this intent. When starting an activity with an implicit intent, Android adds the
+        // DEFAULT_CATEGORY flag otherwise it fails to resolve the intent.
+        ResolveInfo resolvedActivity = mPackageManager.resolveActivity(intent, /* flags= */ 0);
+        if (resolvedActivity != null) {
+            intent.setComponent(resolvedActivity.getComponentInfo().getComponentName());
+        }
+
+        return intent;
     }
 
     /** Helper factory to help with injecting {@link AppClipsViewModel}. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 04de2c2..c1caeed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1774,8 +1774,9 @@
         // the small clock here
         // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
         if (!MigrateClocksToBlueprint.isEnabled()) {
+            boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
             if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
-                    && hasVisibleNotifications() && isOnAod()) {
+                    && hasVisibleNotifications() && (isOnAod() || bypassEnabled)) {
                 return SMALL;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 4d43ad5..a4fed873 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -19,8 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -105,18 +103,26 @@
     @Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
     val legacyExpandImmediate: StateFlow<Boolean>
 
-    val shadeMode: StateFlow<ShadeMode>
-
     /** Whether dual shade should be aligned to the bottom (true) or to the top (false). */
     val isDualShadeAlignedToBottom: Boolean
 
+    /**
+     * Whether the shade layout should be wide (true) or narrow (false).
+     *
+     * In a wide layout, notifications and quick settings each take up only half the screen width
+     * (whether they are shown at the same time or not). In a narrow layout, they can each be as
+     * wide as the entire screen.
+     */
+    val isShadeLayoutWide: StateFlow<Boolean>
+
     /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
     @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
 
     /** NPVC.mClosing as a flow. */
     @Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
 
-    fun setShadeMode(mode: ShadeMode)
+    /** Sets whether the shade layout should be wide (true) or narrow (false). */
+    fun setShadeLayoutWide(isShadeLayoutWide: Boolean)
 
     /** Sets whether a closing animation is happening. */
     @Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
@@ -183,6 +189,7 @@
 class ShadeRepositoryImpl @Inject constructor(@Application applicationContext: Context) :
     ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
+    @Deprecated("Use ShadeInteractor.qsExpansion instead")
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
 
     private val _lockscreenShadeExpansion = MutableStateFlow(0f)
@@ -204,6 +211,7 @@
     @Deprecated("Use ShadeInteractor instead")
     override val legacyShadeTracking: StateFlow<Boolean> = _legacyShadeTracking.asStateFlow()
 
+    @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
     override val legacyLockscreenShadeTracking = MutableStateFlow(false)
 
     private val _legacyQsTracking = MutableStateFlow(false)
@@ -227,20 +235,22 @@
     @Deprecated("Use ShadeInteractor instead")
     override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
 
-    val _shadeMode = MutableStateFlow(if (DualShade.isEnabled) ShadeMode.Dual else ShadeMode.Single)
-    override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
+    private val _isShadeLayoutWide = MutableStateFlow(false)
+    override val isShadeLayoutWide: StateFlow<Boolean> = _isShadeLayoutWide.asStateFlow()
 
     override val isDualShadeAlignedToBottom =
         applicationContext.resources.getBoolean(R.bool.config_dualShadeAlignedToBottom)
 
-    override fun setShadeMode(shadeMode: ShadeMode) {
-        _shadeMode.value = shadeMode
+    override fun setShadeLayoutWide(isShadeLayoutWide: Boolean) {
+        _isShadeLayoutWide.value = isShadeLayoutWide
     }
 
+    @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
         _legacyQsFullscreen.value = legacyQsFullscreen
     }
 
+    @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) {
         _legacyExpandImmediate.value = legacyExpandImmediate
     }
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 ef0a842..c1f8a0b 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
@@ -52,9 +52,21 @@
     /** Are touches allowed on the notification panel? */
     val isShadeTouchable: Flow<Boolean>
 
-    /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
+    /** Whether the shade can be expanded from QQS to QS. */
     val isExpandToQsEnabled: Flow<Boolean>
 
+    /** The version of the shade layout to use. */
+    val shadeMode: StateFlow<ShadeMode>
+
+    /**
+     * Whether the shade layout should be wide (true) or narrow (false).
+     *
+     * In a wide layout, notifications and quick settings each take up only half the screen width
+     * (whether they are shown at the same time or not). In a narrow layout, they can each be as
+     * wide as the entire screen.
+     */
+    val isShadeLayoutWide: StateFlow<Boolean>
+
     /** How to align the shade content. */
     val shadeAlignment: ShadeAlignment
 }
@@ -113,8 +125,6 @@
      * animating.
      */
     val isUserInteractingWithQs: Flow<Boolean>
-
-    val shadeMode: StateFlow<ShadeMode>
 }
 
 fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6226d07..e77aca9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -47,5 +47,6 @@
     override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
     override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
     override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
+    override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean
     override val shadeAlignment: ShadeAlignment = ShadeAlignment.Top
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 55f019b..684a484 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,7 +25,9 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.shared.model.ShadeAlignment
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -60,8 +62,7 @@
 ) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
     override val isShadeEnabled: StateFlow<Boolean> =
         disableFlagsRepository.disableFlags
-            .map { isDisabledByFlags -> isDisabledByFlags.isShadeEnabled() }
-            .distinctUntilChanged()
+            .map { it.isShadeEnabled() }
             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
     override val isQsEnabled: StateFlow<Boolean> =
@@ -102,6 +103,17 @@
             }
         }
 
+    override val isShadeLayoutWide: StateFlow<Boolean> = shadeRepository.isShadeLayoutWide
+
+    override val shadeMode: StateFlow<ShadeMode> =
+        isShadeLayoutWide
+            .map(this::determineShadeMode)
+            .stateIn(
+                scope,
+                SharingStarted.Eagerly,
+                initialValue = determineShadeMode(isShadeLayoutWide.value)
+            )
+
     override val shadeAlignment: ShadeAlignment =
         if (shadeRepository.isDualShadeAlignedToBottom) {
             ShadeAlignment.Bottom
@@ -125,4 +137,12 @@
                 disableFlags.isQuickSettingsEnabled() &&
                 !isDozing
         }
+
+    private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode {
+        return when {
+            DualShade.isEnabled -> ShadeMode.Dual
+            isShadeLayoutWide -> ShadeMode.Split
+            else -> ShadeMode.Single
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 7d46d2b..f39ee9a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -105,8 +104,6 @@
     override val isUserInteractingWithQs: Flow<Boolean> =
         userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
 
-    override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
-
     /**
      * 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
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index d5b4f4d..b2142a5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
@@ -45,10 +43,7 @@
     @Application scope: CoroutineScope,
     sceneInteractor: SceneInteractor,
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
-    shadeRepository: ShadeRepository,
 ) : BaseShadeInteractor {
-    override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
-
     override val shadeExpansion: StateFlow<Float> =
         sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
             .stateIn(scope, SharingStarted.Eagerly, 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 354d379..5eb3a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -29,8 +29,6 @@
 import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.transition.ScrimShadeTransitionController
 import com.android.systemui.statusbar.PulseExpansionHandler
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -40,7 +38,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
@@ -63,7 +60,7 @@
 ) : CoreStartable {
 
     override fun start() {
-        hydrateShadeMode()
+        hydrateShadeLayoutWidth()
         hydrateShadeExpansionStateManager()
         logTouchesTo(touchLog)
         scrimShadeTransitionController.init()
@@ -86,22 +83,17 @@
         }
     }
 
-    private fun hydrateShadeMode() {
-        if (DualShade.isEnabled) {
-            shadeRepository.setShadeMode(ShadeMode.Dual)
-            return
-        }
+    private fun hydrateShadeLayoutWidth() {
         applicationScope.launch {
             configurationRepository.onAnyConfigurationChange
                 // Force initial collection.
                 .onStart { emit(Unit) }
-                .map { applicationContext.resources }
-                .map { resources ->
-                    splitShadeStateController.shouldUseSplitNotificationShade(resources)
-                }
-                .collect { isSplitShade ->
-                    shadeRepository.setShadeMode(
-                        if (isSplitShade) ShadeMode.Split else ShadeMode.Single
+                .collect {
+                    val resources = applicationContext.resources
+                    // The configuration for 'shouldUseSplitNotificationShade' dictates the width of
+                    // the shade in both split-shade and dual-shade modes.
+                    shadeRepository.setShadeLayoutWide(
+                        splitShadeStateController.shouldUseSplitNotificationShade(resources)
                     )
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 2f58b35..ea4e065 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -18,51 +18,35 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
-import com.android.systemui.smartspace.data.repository.SmartspaceRepositoryModule
 import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
 import dagger.Binds
 import dagger.BindsOptionalOf
 import dagger.Module
 import javax.inject.Named
 
-@Module(subcomponents = [SmartspaceViewComponent::class],
-    includes = [SmartspaceRepositoryModule::class])
+@Module(subcomponents = [SmartspaceViewComponent::class])
 abstract class SmartspaceModule {
     @Module
     companion object {
-        /**
-         * The BcSmartspaceDataProvider for dreams.
-         */
+        /** The BcSmartspaceDataProvider for dreams. */
         const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
 
-        /**
-         * The BcSmartspaceDataPlugin for the standalone weather on dream.
-         */
+        /** The BcSmartspaceDataPlugin for the standalone weather on dream. */
         const val DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN = "dream_weather_smartspace_data_plugin"
 
-        /**
-         * The target filter for smartspace over lockscreen.
-         */
+        /** The target filter for smartspace over lockscreen. */
         const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"
 
-        /**
-         * The precondition for smartspace over lockscreen
-         */
+        /** The precondition for smartspace over lockscreen */
         const val LOCKSCREEN_SMARTSPACE_PRECONDITION = "lockscreen_smartspace_precondition"
 
-        /**
-         * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
-         */
+        /** The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd). */
         const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
 
-        /**
-         * The BcSmartspaceDataPlugin for the standalone weather.
-         */
+        /** The BcSmartspaceDataPlugin for the standalone weather. */
         const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
 
-        /**
-         * The BcSmartspaceDataProvider for the glanceable hub.
-         */
+        /** The BcSmartspaceDataProvider for the glanceable hub. */
         const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin"
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
deleted file mode 100644
index 52a1c15..0000000
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.smartspace.data.repository
-
-import android.app.smartspace.SmartspaceTarget
-import android.os.Parcelable
-import android.widget.RemoteViews
-import com.android.systemui.communal.smartspace.CommunalSmartspaceController
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
-
-interface SmartspaceRepository {
-    /** Whether [RemoteViews] are passed through smartspace targets. */
-    val isSmartspaceRemoteViewsEnabled: Boolean
-
-    /** Smartspace targets for the communal surface. */
-    val communalSmartspaceTargets: Flow<List<SmartspaceTarget>>
-}
-
-@SysUISingleton
-class SmartspaceRepositoryImpl
-@Inject
-constructor(
-    private val communalSmartspaceController: CommunalSmartspaceController,
-    @Main private val uiExecutor: Executor,
-) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
-
-    override val isSmartspaceRemoteViewsEnabled: Boolean
-        get() = android.app.smartspace.flags.Flags.remoteViews()
-
-    private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
-        MutableStateFlow(emptyList())
-    override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
-        _communalSmartspaceTargets
-            .onStart {
-                uiExecutor.execute {
-                    communalSmartspaceController.addListener(
-                        listener = this@SmartspaceRepositoryImpl
-                    )
-                }
-            }
-            .onCompletion {
-                uiExecutor.execute {
-                    communalSmartspaceController.removeListener(
-                        listener = this@SmartspaceRepositoryImpl
-                    )
-                }
-            }
-
-    override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
-        targetsNullable?.let { targets ->
-            _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
-        }
-            ?: run { _communalSmartspaceTargets.value = emptyList() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt
deleted file mode 100644
index c77bcc5..0000000
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.smartspace.data.repository
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface SmartspaceRepositoryModule {
-    @Binds fun smartspaceRepository(impl: SmartspaceRepositoryImpl): SmartspaceRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 342ec0d..5bb2936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -274,7 +274,8 @@
                 context.getString(R.string.keyboard_key_button_template, "Mode"));
         mSpecialCharacterNames.put(
                 KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
-        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+        mSpecialCharacterNames.put(
+                KeyEvent.KEYCODE_ESCAPE, context.getString(R.string.keyboard_key_esc));
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 6ee13c3..a49ca38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -259,7 +259,8 @@
                 context.getString(R.string.keyboard_key_button_template, "Mode"));
         mSpecialCharacterNames.put(
                 KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
-        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+        mSpecialCharacterNames.put(
+                KeyEvent.KEYCODE_ESCAPE, context.getString(R.string.keyboard_key_esc));
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index e505ef7..0957e5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -51,6 +51,7 @@
 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
@@ -223,6 +224,10 @@
                         mSceneContainerOcclusionInteractorLazy.get().getInvisibleDueToOcclusion(),
                         this::calculateStateFromSceneFramework),
                     this::onStatusBarStateChanged);
+
+            mJavaAdapter.alwaysCollectFlow(
+                    mKeyguardTransitionInteractorLazy.get().transitionValue(KeyguardState.AOD),
+                    this::onAodKeyguardStateTransitionValueChanged);
         }
     }
 
@@ -693,6 +698,14 @@
         updateStateAndNotifyListeners(newState);
     }
 
+    private void onAodKeyguardStateTransitionValueChanged(float value) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+
+        setDozeAmountInternal(value);
+    }
+
     private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
             Scenes.Lockscreen, StatusBarState.KEYGUARD,
             Scenes.Bouncer, StatusBarState.KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index cac3f25..bafec38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -51,33 +51,34 @@
     }
 
     private fun getMessage(): String {
+        val hostDeviceName = state.projectionState.hostDeviceName
         return if (state.projectionState is MediaProjectionState.Projecting.SingleTask) {
             val appBeingSharedName =
                 endMediaProjectionDialogHelper.getAppName(state.projectionState)
-            if (appBeingSharedName != null && state.deviceName != null) {
+            if (appBeingSharedName != null && hostDeviceName != null) {
                 context.getString(
                     R.string.cast_to_other_device_stop_dialog_message_specific_app_with_device,
                     appBeingSharedName,
-                    state.deviceName,
+                    hostDeviceName,
                 )
             } else if (appBeingSharedName != null) {
                 context.getString(
                     R.string.cast_to_other_device_stop_dialog_message_specific_app,
                     appBeingSharedName,
                 )
-            } else if (state.deviceName != null) {
+            } else if (hostDeviceName != null) {
                 context.getString(
                     R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
-                    state.deviceName
+                    hostDeviceName,
                 )
             } else {
                 context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
             }
         } else {
-            if (state.deviceName != null) {
+            if (hostDeviceName != null) {
                 context.getString(
                     R.string.cast_to_other_device_stop_dialog_message_entire_screen_with_device,
-                    state.deviceName
+                    hostDeviceName,
                 )
             } else {
                 context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 4183cdd..f5e17df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -58,7 +57,6 @@
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val mediaRouterChipInteractor: MediaRouterChipInteractor,
     private val systemClock: SystemClock,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
 ) : OngoingActivityChipViewModel {
     /**
@@ -175,7 +173,6 @@
             startTimeMs = systemClock.elapsedRealtime(),
             createDialogLaunchOnClickListener(
                 createCastScreenToOtherDeviceDialogDelegate(state),
-                dialogTransitionAnimator,
             ),
         )
     }
@@ -191,7 +188,6 @@
             colors = ColorsModel.Red,
             createDialogLaunchOnClickListener(
                 createGenericCastToOtherDeviceDialogDelegate(deviceName),
-                dialogTransitionAnimator,
             ),
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index ce60fab..191c221 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -71,11 +71,11 @@
                             {
                                 str1 = type.name
                                 str2 = state.hostPackage
+                                str3 = state.hostDeviceName
                             },
-                            { "State: Projecting(type=$str1 hostPackage=$str2)" }
+                            { "State: Projecting(type=$str1 hostPackage=$str2 hostDevice=$str3)" }
                         )
-                        // TODO(b/351851835): Get the device name.
-                        ProjectionChipModel.Projecting(type, state, deviceName = null)
+                        ProjectionChipModel.Projecting(type, state)
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
index a1a5e82..85682f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
@@ -26,16 +26,10 @@
     /** There is no media being projected. */
     data object NotProjecting : ProjectionChipModel()
 
-    /**
-     * Media is currently being projected.
-     *
-     * @property deviceName the name of the device receiving the projection, or null if the
-     *   projection is to this device (as opposed to a different device).
-     */
+    /** Media is currently being projected. */
     data class Projecting(
         val type: Type,
         val projectionState: MediaProjectionState.Projecting,
-        val deviceName: String?,
     ) : ProjectionChipModel()
 
     enum class Type {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index df25d57..e201652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -19,7 +19,6 @@
 import android.app.ActivityManager
 import android.content.Context
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -52,7 +51,6 @@
     private val interactor: ScreenRecordChipInteractor,
     private val systemClock: SystemClock,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
 ) : OngoingActivityChipViewModel {
     override val chip: StateFlow<OngoingActivityChipModel> =
         interactor.screenRecordState
@@ -78,7 +76,6 @@
                             startTimeMs = systemClock.elapsedRealtime(),
                             createDialogLaunchOnClickListener(
                                 createDelegate(state.recordedTask),
-                                dialogTransitionAnimator,
                             ),
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index c097720..45260e18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -52,7 +51,6 @@
     private val context: Context,
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val systemClock: SystemClock,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
 ) : OngoingActivityChipViewModel {
     override val chip: StateFlow<OngoingActivityChipModel> =
@@ -89,10 +87,7 @@
             colors = ColorsModel.Red,
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(
-                createShareToAppDialogDelegate(state),
-                dialogTransitionAnimator
-            ),
+            createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state)),
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 0dbf5d6..65f94ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,10 +17,7 @@
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
 import android.view.View
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlinx.coroutines.flow.StateFlow
 
@@ -36,19 +33,10 @@
         /** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
         fun createDialogLaunchOnClickListener(
             dialogDelegate: SystemUIDialog.Delegate,
-            dialogTransitionAnimator: DialogTransitionAnimator,
         ): View.OnClickListener {
             return View.OnClickListener { view ->
                 val dialog = dialogDelegate.createDialog()
-                val launchableView =
-                    view.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                // TODO(b/343699052): This makes a beautiful animate-in, but the
-                //  animate-out looks odd because the dialog animates back into the chip
-                //  but then the chip disappears. If we aren't able to address
-                //  b/343699052 in time for launch, we should just use `dialog.show`.
-                dialogTransitionAnimator.showFromView(dialog, launchableView)
+                dialog.show()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
index f166d32..5adf31b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
@@ -13,18 +13,12 @@
     /** The subset of active listeners which are temporary (will be removed after called) */
     private val temporaryListeners = ArraySet<OnReorderingAllowedListener>()
 
-    private val banListeners = ListenerSet<OnReorderingBannedListener>()
-
     var isReorderingAllowed = true
         set(value) {
             if (field != value) {
                 field = value
                 if (value) {
                     notifyReorderingAllowed()
-                } else {
-                    banListeners.forEach { listener ->
-                        listener.onReorderingBanned()
-                    }
                 }
             }
         }
@@ -44,10 +38,6 @@
         allListeners.addIfAbsent(listener)
     }
 
-    fun addPersistentReorderingBannedListener(listener: OnReorderingBannedListener) {
-        banListeners.addIfAbsent(listener)
-    }
-
     /** Add a listener which will be removed when it is called. */
     fun addTemporaryReorderingAllowedListener(listener: OnReorderingAllowedListener) {
         // Only add to the temporary set if it was added to the global set
@@ -67,8 +57,3 @@
 fun interface OnReorderingAllowedListener {
     fun onReorderingAllowed()
 }
-
-fun interface OnReorderingBannedListener {
-    fun onReorderingBanned()
-}
-
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 9e943ee..582d847 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
@@ -371,12 +371,6 @@
                 setUserExpanded(nowExpanded);
             }
 
-            if (ExpandHeadsUpOnInlineReply.isEnabled() && mExpandable) {
-                // it is triggered by the user.
-                // So, mHasUserChangedExpansion should be marked true.
-                mHasUserChangedExpansion = true;
-            }
-
             notifyHeightChanged(/* needsAnimation= */ true);
             mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
             if (shouldLogExpandClickMetric) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index b5ea861..b8af369 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.app.Notification
+import android.app.Notification.RichOngoingStyle
 import android.app.PendingIntent
 import android.content.Context
 import android.util.Log
@@ -68,12 +69,14 @@
         builder: Notification.Builder,
         systemUIContext: Context,
         packageContext: Context
-    ): RichOngoingContentModel? =
+    ): RichOngoingContentModel? {
+        if (builder.style !is RichOngoingStyle) return null
+
         try {
             val sbn = entry.sbn
             val notification = sbn.notification
             val icon = IconModel(notification.smallIcon)
-            if (sbn.packageName == "com.google.android.deskclock") {
+            return if (sbn.packageName == "com.google.android.deskclock") {
                 when (notification.channelId) {
                     "Timers v2" -> {
                         parseTimerNotification(notification, icon)
@@ -90,8 +93,9 @@
             } else null
         } catch (e: Exception) {
             Log.e("RONs", "Error parsing RON", e)
-            null
+            return null
         }
+    }
 
     /**
      * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dab3799..1789ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -715,6 +715,7 @@
     }
 
     public float getNotificationSquishinessFraction() {
+        SceneContainerFlag.assertInLegacyMode();
         return mStackScrollAlgorithm.getNotificationSquishinessFraction(mAmbientState);
     }
 
@@ -860,7 +861,7 @@
                     /* label= */ "getHeight() - mKeyguardBottomPadding = " + y);
         }
 
-        y = getHeight() - getEmptyBottomMargin();
+        y = getHeight() - getEmptyBottomMarginInternal();
         drawDebugInfo(canvas, y, Color.GREEN,
                 /* label= */ "getHeight() - getEmptyBottomMargin() = " + y);
 
@@ -1270,7 +1271,7 @@
 
     private void updateAlgorithmLayoutMinHeight() {
         mAmbientState.setLayoutMinHeight(mQsFullScreen || isHeadsUpTransition()
-                ? getLayoutMinHeight() : 0);
+                ? getLayoutMinHeightInternal() : 0);
     }
 
     /**
@@ -1444,7 +1445,7 @@
         } else {
             if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
                 final float endHeight = updateStackEndHeight(
-                        getHeight(), getEmptyBottomMargin(), getTopPadding());
+                        getHeight(), getEmptyBottomMarginInternal(), getTopPadding());
                 updateStackHeight(endHeight, fraction);
             } else {
                 // Always updateStackHeight to prevent jumps in the stack height when this fraction
@@ -1617,7 +1618,7 @@
         float appear;
         float expandAmount;
         if (mKeyguardBypassEnabled && onKeyguard()) {
-            appear = calculateAppearFractionBypass();
+            appear = calculateAppearFractionBypassInternal();
             expandAmount = getPulseHeight();
         } else {
             appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight));
@@ -1642,6 +1643,7 @@
      * Return the height of the content ignoring the footer.
      */
     public int getIntrinsicContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return (int) mIntrinsicContentHeight;
     }
 
@@ -2373,6 +2375,11 @@
      * @return the first child which has visibility unequal to GONE
      */
     public ExpandableView getFirstChildNotGone() {
+        SceneContainerFlag.assertInLegacyMode();
+        return getFirstChildNotGoneInternal();
+    }
+
+    private ExpandableView getFirstChildNotGoneInternal() {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
@@ -2432,6 +2439,7 @@
      * @return the number of children which have visibility unequal to GONE
      */
     public int getNotGoneChildCount() {
+        SceneContainerFlag.assertInLegacyMode();
         int childCount = getChildCount();
         int count = 0;
         for (int i = 0; i < childCount; i++) {
@@ -2642,7 +2650,7 @@
      */
     public void updateTopPadding(float qsHeight, boolean animate) {
         int topPadding = (int) qsHeight;
-        int minStackHeight = getLayoutMinHeight();
+        int minStackHeight = getLayoutMinHeightInternal();
         if (topPadding + minStackHeight > getHeight()) {
             mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
         } else {
@@ -2658,6 +2666,11 @@
     }
 
     public int getLayoutMinHeight() {
+        SceneContainerFlag.assertInLegacyMode();
+        return getLayoutMinHeightInternal();
+    }
+
+    private int getLayoutMinHeightInternal() {
         if (isHeadsUpTransition()) {
             ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow();
             if (trackedHeadsUpRow.isAboveShelf()) {
@@ -3430,7 +3443,7 @@
      * @return Whether a y coordinate is inside the content.
      */
     public boolean isInContentBounds(float y) {
-        return y < getHeight() - getEmptyBottomMargin();
+        return y < getHeight() - getEmptyBottomMarginInternal();
     }
 
     private float getTouchSlop(MotionEvent event) {
@@ -4063,10 +4076,16 @@
     }
 
     boolean isScrolledToBottom() {
+        SceneContainerFlag.assertInLegacyMode();
         return mScrollAdapter.isScrolledToBottom();
     }
 
     int getEmptyBottomMargin() {
+        SceneContainerFlag.assertInLegacyMode();
+        return getEmptyBottomMarginInternal();
+    }
+
+    private int getEmptyBottomMarginInternal() {
         int contentHeight;
         if (mShouldUseSplitNotificationShade) {
             // When in split shade and there are no notifications, the height can be too low, as
@@ -4249,7 +4268,7 @@
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
         if (view instanceof ExpandableNotificationRow row && !onKeyguard()) {
             // TODO: once we're recycling this will need to check the adapter position of the child
-            if (row.isUserLocked() && row != getFirstChildNotGone()) {
+            if (row.isUserLocked() && row != getFirstChildNotGoneInternal()) {
                 if (row.isSummaryWithChildren()) {
                     return;
                 }
@@ -4536,7 +4555,7 @@
         // clipped when pulsing
         float ownTranslationZ = 0;
         if (mKeyguardBypassEnabled && mAmbientState.isHiddenAtAll()) {
-            ExpandableView firstChildNotGone = getFirstChildNotGone();
+            ExpandableView firstChildNotGone = getFirstChildNotGoneInternal();
             if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
                 ownTranslationZ = firstChildNotGone.getTranslationZ();
             }
@@ -4684,10 +4703,12 @@
     }
 
     public int getEmptyShadeViewHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mEmptyShadeView.getHeight();
     }
 
     public float getBottomMostNotificationBottom() {
+        SceneContainerFlag.assertInLegacyMode();
         final int count = getChildCount();
         float max = 0;
         for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -5110,6 +5131,7 @@
     }
 
     public float getOpeningHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         if (mEmptyShadeView.getVisibility() == GONE) {
             return getMinExpansionHeight();
         } else {
@@ -5566,6 +5588,11 @@
     }
 
     public float calculateAppearFractionBypass() {
+        SceneContainerFlag.assertInLegacyMode();
+        return calculateAppearFractionBypassInternal();
+    }
+
+    private float calculateAppearFractionBypassInternal() {
         float pulseHeight = getPulseHeight();
         // The total distance required to fully reveal the header
         float totalDistance = getIntrinsicPadding();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 36930bf..726fdee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1004,6 +1004,7 @@
     }
 
     public int getRight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getRight();
     }
 
@@ -1015,6 +1016,7 @@
      * @return the left of the view.
      */
     public int getLeft() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getLeft();
     }
 
@@ -1022,6 +1024,7 @@
      * @return the top of the view.
      */
     public int getTop() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getTop();
     }
 
@@ -1029,6 +1032,7 @@
      * @return the bottom of the view.
      */
     public int getBottom() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getBottom();
     }
 
@@ -1158,6 +1162,7 @@
     }
 
     public int getIntrinsicContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getIntrinsicContentHeight();
     }
 
@@ -1216,6 +1221,7 @@
     }
 
     public float getX() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getX();
     }
 
@@ -1224,14 +1230,17 @@
     }
 
     public float getWidth() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getWidth();
     }
 
     public float getOpeningHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getOpeningHeight();
     }
 
     public float getBottomMostNotificationBottom() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getBottomMostNotificationBottom();
     }
 
@@ -1266,10 +1275,12 @@
     }
 
     public float getNotificationSquishinessFraction() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getNotificationSquishinessFraction();
     }
 
     public float calculateAppearFractionBypass() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.calculateAppearFractionBypass();
     }
 
@@ -1279,22 +1290,27 @@
     }
 
     public boolean isScrolledToBottom() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.isScrolledToBottom();
     }
 
     public int getNotGoneChildCount() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getNotGoneChildCount();
     }
 
     public float getIntrinsicPadding() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getIntrinsicPadding();
     }
 
     public float getLayoutMinHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getLayoutMinHeight();
     }
 
     public int getEmptyBottomMargin() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getEmptyBottomMargin();
     }
 
@@ -1307,6 +1323,7 @@
     }
 
     public float getEmptyShadeViewHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getEmptyShadeViewHeight();
     }
 
@@ -1474,6 +1491,7 @@
     }
 
     public boolean isShowingEmptyShadeView() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.isEmptyShadeViewVisible();
     }
 
@@ -1602,6 +1620,7 @@
     }
 
     public ExpandableView getFirstChildNotGone() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getFirstChildNotGone();
     }
 
@@ -1713,6 +1732,7 @@
     }
 
     public boolean isLongPressInProgress() {
+        SceneContainerFlag.assertInLegacyMode();
         return mLongPressedView != null;
     }
 
@@ -1722,6 +1742,7 @@
      * from the keyguard host to the quick settings one.
      */
     public int getFullShadeTransitionInset() {
+        SceneContainerFlag.assertInLegacyMode();
         MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
         if (view == null || view.getHeight() == 0
                 || mStatusBarStateController.getState() != KEYGUARD) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 497ffca..6881d11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -38,12 +38,14 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SharedNotificationContainerBinder
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 91b5d0b..a538856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -414,6 +414,14 @@
                     }
                 }
 
+                @Override
+                public void onFeatureEnabledChanged(boolean enabled) {
+                    if (!enabled) {
+                        mHost.removeTile(BRIGHTNESS);
+                        mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
+                    }
+                }
+
                 private void addReduceBrightColorsTile() {
                     if (mAutoTracker.isAdded(BRIGHTNESS)) return;
                     mHost.addTile(BRIGHTNESS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index bd0097e..398c1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -74,9 +74,9 @@
             mLightModeIconColorSingleTone = Color.WHITE;
         } else {
             mDarkModeIconColorSingleTone = context.getColor(
-                    com.android.settingslib.R.color.black);
+                    com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
             mLightModeIconColorSingleTone = context.getColor(
-                    com.android.settingslib.R.color.white);
+                    com.android.settingslib.R.color.light_mode_icon_color_single_tone);
         }
 
         mTransitionsController = lightBarTransitionsControllerFactory.create(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0623bb2c..a2e44df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -31,7 +31,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -40,7 +39,6 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
@@ -88,7 +86,7 @@
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
     private final VisualStabilityProvider mVisualStabilityProvider;
 
-    private AvalancheController mAvalancheController;
+    private final AvalancheController mAvalancheController;
 
     // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
     private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
@@ -173,11 +171,8 @@
                 updateResources();
             }
         });
-        if (!NotificationsHeadsUpRefactor.isEnabled()) {
-            javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
-        }
-        mVisualStabilityProvider.addPersistentReorderingBannedListener(mOnReorderingBannedListener);
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -272,10 +267,9 @@
     }
 
     private void onShadeOrQsExpanded(Boolean isExpanded) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
-            if (isExpanded) {
+            if (!NotificationsHeadsUpRefactor.isEnabled() && isExpanded) {
                 mHeadsUpAnimatingAway.setValue(false);
             }
         }
@@ -385,8 +379,6 @@
 
     private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-        mAvalancheController.setEnableAtRuntime(true);
-
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (isHeadsUpEntry(entry.getKey())) {
                 // Maybe the heads-up was removed already
@@ -397,29 +389,6 @@
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
     };
 
-    private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
-        if (mAvalancheController != null) {
-            // Waiting HUNs in AvalancheController are still promoted to the HUN section and thus
-            // seen in open shade; clear them so we don't show them again when the shade closes and
-            // reordering is allowed again.
-            final int numDropped = mAvalancheController.getWaitingKeys().size();
-            mAvalancheController.logDroppedHunsInBackground(numDropped);
-            mAvalancheController.clearNext();
-
-            // In open shade the first HUN is pinned, and visual stability logic prevents us from
-            // unpinning this first HUN as long as the shade remains open. AvalancheController only
-            // shows the next HUN when the currently showing HUN is unpinned, so we must disable
-            // throttling here so that the incoming HUN stream is not forever paused. This is reset
-            // when reorder becomes allowed.
-            mAvalancheController.setEnableAtRuntime(false);
-
-            // Note that we cannot do the above when
-            // 1) the remove runnable runs because its delay means it may not run before shade close
-            // 2) reordering is allowed again (when shade closes) because the HUN appear animation
-            // will have started by then
-        }
-    };
-
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  HeadsUpManager utility (protected) methods overrides:
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index d0a62e7..84e6018 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -455,8 +455,8 @@
         float luminance = Color.luminance(textColor);
         @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
                     luminance < 0.5
-                        ? com.android.settingslib.R.color.black
-                        : com.android.settingslib.R.color.white);
+                        ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
+                        : com.android.settingslib.R.color.light_mode_icon_color_single_tone);
         @ColorInt int contrastColor = luminance < 0.5
                 ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
                 : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
@@ -467,7 +467,7 @@
         if (userSwitcherName != null) {
             userSwitcherName.setTextColor(Utils.getColorStateListDefaultColor(
                     mContext,
-                    com.android.settingslib.R.color.white));
+                    com.android.settingslib.R.color.light_mode_icon_color_single_tone));
         }
 
         if (iconManager != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 824415e..231a8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -39,7 +39,7 @@
 ) {
     override fun toString(): String {
         val appearanceString =
-            ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+                ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
         return "LetterboxAppearance{$appearanceString, $appearanceRegions}"
     }
 }
@@ -57,16 +57,14 @@
     private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
 ) : Dumpable {
 
-    private val darkAppearanceIconColor =
-        context.getColor(
-            // For a dark background status bar, use a *light* icon color.
-            com.android.settingslib.R.color.white
-        )
-    private val lightAppearanceIconColor =
-        context.getColor(
-            // For a light background status bar, use a *dark* icon color.
-            com.android.settingslib.R.color.black
-        )
+    private val darkAppearanceIconColor = context.getColor(
+        // For a dark background status bar, use a *light* icon color.
+        com.android.settingslib.R.color.light_mode_icon_color_single_tone
+    )
+    private val lightAppearanceIconColor = context.getColor(
+        // For a light background status bar, use a *dark* icon color.
+        com.android.settingslib.R.color.dark_mode_icon_color_single_tone
+    )
 
     init {
         dumpManager.registerCriticalDumpable(this)
@@ -87,11 +85,7 @@
         lastAppearanceRegions = originalAppearanceRegions
         lastLetterboxes = letterboxes
         return getLetterboxAppearanceInternal(
-                letterboxes,
-                originalAppearance,
-                originalAppearanceRegions,
-                statusBarBounds
-            )
+                letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds)
             .also { lastLetterboxAppearance = it }
     }
 
@@ -144,9 +138,7 @@
                 // full bounds of its window.
                 // Here we want the bounds to be only for the inner bounds of the letterboxed app.
                 AppearanceRegion(
-                    appearanceRegion.appearance,
-                    matchingLetterbox.letterboxInnerBounds
-                )
+                    appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
             }
         }
 
@@ -156,8 +148,7 @@
     ): LetterboxAppearance {
         return LetterboxAppearance(
             originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
-            originalAppearanceRegions
-        )
+            originalAppearanceRegions)
     }
 
     @Appearance
@@ -224,9 +215,7 @@
            lastAppearanceRegion: $lastAppearanceRegions,
            lastLetterboxes: $lastLetterboxes,
            lastLetterboxAppearance: $lastLetterboxAppearance
-       """
-                .trimIndent()
-        )
+       """.trimIndent())
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e4edfa4..069c624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -474,6 +474,7 @@
 
         // PRIMARY_BOUNCER->GONE
         collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.getINVALID(),
                 Edge.Companion.create(PRIMARY_BOUNCER, GONE)),
                 mBouncerToGoneTransition, mMainDispatcher);
         collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 507759c..68163b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -325,7 +325,7 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), true)
 
-    private val shownLevel: StateFlow<Int> =
+    private val cellularShownLevel: StateFlow<Int> =
         combine(
                 level,
                 isInService,
@@ -337,15 +337,19 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
+    // Satellite level is unaffected by the isInService or inflateSignalStrength properties
+    // See b/346904529 for details
+    private val satelliteShownLevel: StateFlow<Int> = level
+
     private val cellularIcon: Flow<SignalIconModel.Cellular> =
         combine(
-            shownLevel,
+            cellularShownLevel,
             numberOfLevels,
             showExclamationMark,
             carrierNetworkChangeActive,
-        ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+        ) { cellularShownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
             SignalIconModel.Cellular(
-                shownLevel,
+                cellularShownLevel,
                 numberOfLevels,
                 showExclamationMark,
                 carrierNetworkChange,
@@ -353,7 +357,7 @@
         }
 
     private val satelliteIcon: Flow<SignalIconModel.Satellite> =
-        shownLevel.map {
+        satelliteShownLevel.map {
             SignalIconModel.Satellite(
                 level = it,
                 icon =
@@ -365,7 +369,7 @@
     override val signalLevelIcon: StateFlow<SignalIconModel> = run {
         val initial =
             SignalIconModel.Cellular(
-                shownLevel.value,
+                cellularShownLevel.value,
                 numberOfLevels.value,
                 showExclamationMark.value,
                 carrierNetworkChangeActive.value,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 8aabdf2..43ab337 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -44,7 +44,6 @@
 
     private val tag = "AvalancheController"
     private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
-    var enableAtRuntime = true
 
     // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
     @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -87,17 +86,13 @@
         dumpManager.registerNormalDumpable(tag, /* module */ this)
     }
 
-    fun isEnabled() : Boolean {
-        return NotificationThrottleHun.isEnabled && enableAtRuntime
-    }
-
     fun getShowingHunKey(): String {
         return getKey(headsUpEntryShowing)
     }
 
     /** Run or delay Runnable for given HeadsUpEntry */
     fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             runnable.run()
             return
         }
@@ -153,7 +148,7 @@
      * all Runnables associated with that entry.
      */
     fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             runnable.run()
             return
         }
@@ -194,7 +189,7 @@
      *    BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
      */
     fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             // Use default duration, like we did before AvalancheController existed
             return autoDismissMs
         }
@@ -243,7 +238,7 @@
 
     /** Return true if entry is waiting to show. */
     fun isWaiting(key: String): Boolean {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             return false
         }
         for (entry in nextMap.keys) {
@@ -256,7 +251,7 @@
 
     /** Return list of keys for huns waiting */
     fun getWaitingKeys(): MutableList<String> {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             return mutableListOf()
         }
         val keyList = mutableListOf<String>()
@@ -267,7 +262,7 @@
     }
 
     fun getWaitingEntry(key: String): HeadsUpEntry? {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             return null
         }
         for (headsUpEntry in nextMap.keys) {
@@ -279,7 +274,7 @@
     }
 
     fun getWaitingEntryList(): List<HeadsUpEntry> {
-        if (!isEnabled()) {
+        if (!NotificationThrottleHun.isEnabled) {
             return mutableListOf()
         }
         return nextMap.keys.toList()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
index d120a1b..72d093c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
@@ -22,10 +22,10 @@
 
     /** Returns true if the device should use the split notification shade. */
     @Deprecated(
-        message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+        message = "This is deprecated, please use ShadeInteractor#shadeMode instead",
         replaceWith =
             ReplaceWith(
-                "shadeInteractor.isSplitShade",
+                "shadeInteractor.shadeMode",
                 "com.android.systemui.shade.domain.interactor.ShadeInteractor",
             ),
     )
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
deleted file mode 100644
index d54c07c..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2017 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.tuner;
-
-import android.util.DisplayMetrics;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.tuner.TunerService.Tunable;
-
-import javax.inject.Inject;
-
-/**
- * Version of Space that can be resized by a tunable setting.
- */
-public class TunablePadding implements Tunable {
-
-    public static final int FLAG_START = 1;
-    public static final int FLAG_END = 2;
-    public static final int FLAG_TOP = 4;
-    public static final int FLAG_BOTTOM = 8;
-
-    private final int mFlags;
-    private final View mView;
-    private final int mDefaultSize;
-    private final float mDensity;
-    private final TunerService mTunerService;
-
-    private TunablePadding(String key, int def, int flags, View view, TunerService tunerService) {
-        mDefaultSize = def;
-        mFlags = flags;
-        mView = view;
-        DisplayMetrics metrics = new DisplayMetrics();
-        view.getContext().getSystemService(WindowManager.class)
-                .getDefaultDisplay().getMetrics(metrics);
-        mDensity = metrics.density;
-        mTunerService = tunerService;
-        mTunerService.addTunable(this, key);
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        int dimen = mDefaultSize;
-        if (newValue != null) {
-            try {
-                dimen = (int) (Integer.parseInt(newValue) * mDensity);
-            } catch (NumberFormatException ex) {}
-        }
-        int left = mView.isLayoutRtl() ? FLAG_END : FLAG_START;
-        int right = mView.isLayoutRtl() ? FLAG_START : FLAG_END;
-        mView.setPadding(getPadding(dimen, left), getPadding(dimen, FLAG_TOP),
-                getPadding(dimen, right), getPadding(dimen, FLAG_BOTTOM));
-    }
-
-    private int getPadding(int dimen, int flag) {
-        return ((mFlags & flag) != 0) ? dimen : 0;
-    }
-
-    public void destroy() {
-        mTunerService.removeTunable(this);
-    }
-
-    /**
-     * Exists for easy injecting in tests.
-     */
-    @SysUISingleton
-    public static class TunablePaddingService {
-
-        private final TunerService mTunerService;
-
-        /**
-         */
-        @Inject
-        public TunablePaddingService(TunerService tunerService) {
-            mTunerService = tunerService;
-        }
-
-        public TunablePadding add(View view, String key, int defaultSize, int flags) {
-            if (view == null) {
-                throw new IllegalArgumentException();
-            }
-            return new TunablePadding(key, defaultSize, flags, view, mTunerService);
-        }
-    }
-
-    public static TunablePadding addTunablePadding(View view, String key, int defaultSize,
-            int flags) {
-        return Dependency.get(TunablePaddingService.class).add(view, key, defaultSize, flags);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index ec7aabb..f2132248 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -20,6 +20,7 @@
 import android.util.IndentingPrintWriter
 import android.view.View
 import android.view.ViewGroup
+import dagger.Lazy
 import java.io.PrintWriter
 
 /** [Sequence] that yields all of the direct children of this [ViewGroup] */
@@ -56,3 +57,8 @@
         getBoundsOnScreen(bounds)
         return bounds
     }
+
+/** Extension method to convert [dagger.Lazy] to [kotlin.Lazy] for object of any class [T]. */
+fun <T> Lazy<T>.toKotlinLazy(): kotlin.Lazy<T> {
+    return lazy { this.get() }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
index ee00e8b..e6e2a07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
@@ -35,3 +35,17 @@
         }
         .onStart { emit(isReduceBrightColorsActivated) }
 }
+
+fun ReduceBrightColorsController.isAvailable(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val callback =
+                object : ReduceBrightColorsController.Listener {
+                    override fun onFeatureEnabledChanged(enabled: Boolean) {
+                        trySend(enabled)
+                    }
+                }
+            addCallback(callback)
+            awaitClose { removeCallback(callback) }
+        }
+        .onStart { emit(isReduceBrightColorsFeatureAvailable) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index fe54044..b5934ec 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -19,6 +19,7 @@
 import android.database.ContentObserver
 import android.net.Uri
 import android.provider.Settings.SettingNotFoundException
+import androidx.annotation.AnyThread
 import androidx.annotation.WorkerThread
 import com.android.app.tracing.TraceUtils.trace
 import kotlinx.coroutines.CoroutineDispatcher
@@ -57,7 +58,7 @@
      * @param name to look up in the table
      * @return the corresponding content URI, or null if not present
      */
-    fun getUriFor(name: String): Uri
+    @AnyThread fun getUriFor(name: String): Uri
 
     /**
      * Registers listener for a given content observer <b>while blocking the current thread</b>.
@@ -89,12 +90,31 @@
      *
      * API corresponding to [registerContentObserver] for Java usage.
      */
+    @AnyThread
     fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
         CoroutineScope(backgroundDispatcher).launch {
             registerContentObserverSync(getUriFor(name), settingsObserver)
         }
 
     /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage. After registration is
+     * complete, the callback block is called on the <b>background thread</b> to allow for update of
+     * value.
+     */
+    @AnyThread
+    fun registerContentObserverAsync(
+        name: String,
+        settingsObserver: ContentObserver,
+        @WorkerThread registered: Runnable
+    ) =
+        CoroutineScope(backgroundDispatcher).launch {
+            registerContentObserverSync(getUriFor(name), settingsObserver)
+            registered.run()
+        }
+
+    /**
      * Registers listener for a given content observer <b>while blocking the current thread</b>.
      *
      * This should not be called from the main thread, use [registerContentObserver] or
@@ -120,6 +140,7 @@
      *
      * API corresponding to [registerContentObserver] for Java usage.
      */
+    @AnyThread
     fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
         CoroutineScope(backgroundDispatcher).launch {
             registerContentObserverSync(uri, settingsObserver)
@@ -128,8 +149,27 @@
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver].'
      *
+     * API corresponding to [registerContentObserver] for Java usage. After registration is
+     * complete, the callback block is called on the <b>background thread</b> to allow for update of
+     * value.
+     */
+    @AnyThread
+    fun registerContentObserverAsync(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        @WorkerThread registered: Runnable
+    ) =
+        CoroutineScope(backgroundDispatcher).launch {
+            registerContentObserverSync(uri, settingsObserver)
+            registered.run()
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
      * Implicitly calls [getUriFor] on the passed in name.
      */
+    @WorkerThread
     fun registerContentObserverSync(
         name: String,
         notifyForDescendants: Boolean,
@@ -158,6 +198,7 @@
      *
      * API corresponding to [registerContentObserver] for Java usage.
      */
+    @AnyThread
     fun registerContentObserverAsync(
         name: String,
         notifyForDescendants: Boolean,
@@ -168,6 +209,25 @@
         }
 
     /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage. After registration is
+     * complete, the callback block is called on the <b>background thread</b> to allow for update of
+     * value.
+     */
+    @AnyThread
+    fun registerContentObserverAsync(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        @WorkerThread registered: Runnable
+    ) =
+        CoroutineScope(backgroundDispatcher).launch {
+            registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+            registered.run()
+        }
+
+    /**
      * Registers listener for a given content observer <b>while blocking the current thread</b>.
      *
      * This should not be called from the main thread, use [registerContentObserver] or
@@ -207,6 +267,7 @@
      *
      * API corresponding to [registerContentObserver] for Java usage.
      */
+    @AnyThread
     fun registerContentObserverAsync(
         uri: Uri,
         notifyForDescendants: Boolean,
@@ -217,6 +278,25 @@
         }
 
     /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage. After registration is
+     * complete, the callback block is called on the <b>background thread</b> to allow for update of
+     * value.
+     */
+    @AnyThread
+    fun registerContentObserverAsync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        @WorkerThread registered: Runnable
+    ) =
+        CoroutineScope(backgroundDispatcher).launch {
+            registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
+            registered.run()
+        }
+
+    /**
      * Unregisters the given content observer <b>while blocking the current thread</b>.
      *
      * This should not be called from the main thread, use [unregisterContentObserver] or
@@ -246,6 +326,7 @@
      * API corresponding to [unregisterContentObserver] for Java usage to ensure that
      * [ContentObserver] registration happens on a worker thread.
      */
+    @AnyThread
     fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
         CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) }
 
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 f34058b..20cb1e1 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
@@ -59,12 +59,14 @@
 
     private val testHandler = FakeHandler(Looper.getMainLooper())
     private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val defaultDisplay =
+        display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
 
     private lateinit var displayRepository: DisplayRepositoryImpl
 
     @Before
     fun setup() {
-        setDisplays(DEFAULT_DISPLAY)
+        setDisplays(listOf(defaultDisplay))
         setAllDisplaysIncludingDisabled(DEFAULT_DISPLAY)
         displayRepository =
             DisplayRepositoryImpl(
@@ -434,19 +436,12 @@
     fun defaultDisplayOff_changes() =
         testScope.runTest {
             val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
-            setDisplays(
-                listOf(
-                    display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_OFF)
-                )
-            )
+
+            whenever(defaultDisplay.state).thenReturn(Display.STATE_OFF)
             displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
             assertThat(defaultDisplayOff).isTrue()
 
-            setDisplays(
-                listOf(
-                    display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
-                )
-            )
+            whenever(defaultDisplay.state).thenReturn(Display.STATE_ON)
             displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
             assertThat(defaultDisplayOff).isFalse()
         }
@@ -545,7 +540,10 @@
     }
 
     private fun setAllDisplaysIncludingDisabled(vararg ids: Int) {
-        val displays = ids.map { display(type = TYPE_EXTERNAL, id = it) }.toTypedArray()
+        val displays =
+            (ids.toSet() - DEFAULT_DISPLAY) // Default display always added.
+                .map { display(type = TYPE_EXTERNAL, id = it) }
+                .toTypedArray() + defaultDisplay
         whenever(
                 displayManager.getDisplays(
                     eq(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 765cd01..4fba7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -220,7 +220,7 @@
     val imeGroups = listOf(standardGroup1, standardGroup2, standardGroup3)
     val imeCategory =
         ShortcutCategory(
-            type = ShortcutCategoryType.IME,
+            type = ShortcutCategoryType.InputMethodEditor,
             subCategories =
                 listOf(
                     subCategoryForInputLanguageSwitchShortcuts,
@@ -233,14 +233,14 @@
     val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
     val systemCategory =
         ShortcutCategory(
-            type = ShortcutCategoryType.SYSTEM,
+            type = ShortcutCategoryType.System,
             subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1)
         )
 
     val multitaskingGroups = listOf(standardGroup2, standardGroup1)
     val multitaskingCategory =
         ShortcutCategory(
-            type = ShortcutCategoryType.MULTI_TASKING,
+            type = ShortcutCategoryType.MultiTasking,
             subCategories = listOf(standardSubCategory2, standardSubCategory1)
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index 4c1e869..d20ce3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -23,9 +23,9 @@
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
 import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
@@ -117,7 +117,7 @@
                     TestShortcuts.systemCategory,
                     TestShortcuts.multitaskingCategory,
                     ShortcutCategory(
-                        type = IME,
+                        type = InputMethodEditor,
                         subCategories =
                             TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels
                     ),
@@ -137,7 +137,7 @@
             assertThat(categories)
                 .containsExactly(
                     ShortcutCategory(
-                        type = SYSTEM,
+                        type = System,
                         subCategories =
                             TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
                     ),
@@ -160,7 +160,7 @@
                 .containsExactly(
                     TestShortcuts.systemCategory,
                     ShortcutCategory(
-                        type = MULTI_TASKING,
+                        type = MultiTasking,
                         subCategories =
                             TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
                     ),
@@ -182,7 +182,7 @@
                     TestShortcuts.systemCategory,
                     TestShortcuts.multitaskingCategory,
                     ShortcutCategory(
-                        type = IME,
+                        type = InputMethodEditor,
                         subCategories =
                             TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved
                     ),
@@ -201,7 +201,7 @@
             assertThat(categories)
                 .containsExactly(
                     ShortcutCategory(
-                        type = SYSTEM,
+                        type = System,
                         subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
                     ),
                     TestShortcuts.multitaskingCategory,
@@ -222,7 +222,7 @@
                 .containsExactly(
                     TestShortcuts.systemCategory,
                     ShortcutCategory(
-                        type = MULTI_TASKING,
+                        type = MultiTasking,
                         subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
                     ),
                     TestShortcuts.imeCategory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index f0ad510..59f16d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -84,7 +83,7 @@
     fun testAppliesDefaultBlueprint() {
         testScope.runTest {
             val blueprintId by collectLastValue(underTest.blueprintId)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
             configurationRepository.onConfigurationChange()
 
             runCurrent()
@@ -98,7 +97,7 @@
     fun testAppliesSplitShadeBlueprint() {
         testScope.runTest {
             val blueprintId by collectLastValue(underTest.blueprintId)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             configurationRepository.onConfigurationChange()
 
             runCurrent()
@@ -112,7 +111,7 @@
     fun testDoesNotApplySplitShadeBlueprint() {
         testScope.runTest {
             val blueprintId by collectLastValue(underTest.blueprintId)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
             clockRepository.setCurrentClock(clockController)
             configurationRepository.onConfigurationChange()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 974e3bb..8bc0a60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.setSceneTransition
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -75,7 +74,6 @@
                 transitionInteractor = kosmos.keyguardTransitionInteractor,
                 dismissInteractor = dismissInteractorWithDependencies.interactor,
                 applicationScope = testScope.backgroundScope,
-                sceneInteractor = kosmos.sceneInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index 22181f8..7e249e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -24,10 +24,12 @@
 import android.content.mockedContext
 import android.os.PowerManager
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -78,6 +80,7 @@
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testCanWakeDirectlyToGone_keyguardServiceEnabledThenDisabled() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -114,6 +117,7 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -178,6 +182,7 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testCanWakeDirectlyToGone_wakeAndUnlock() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -201,6 +206,7 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testCanWakeDirectlyToGone_andSetsAlarm_ifPowerButtonDoesNotLockImmediately() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -224,6 +230,7 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testSetsCanIgnoreAuth_andSetsAlarm_whenTimingOut() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -267,6 +274,7 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testCancelsFirstAlarm_onWake_withSecondAlarmSet() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 4a39a9b..6e381ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
@@ -48,6 +47,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -57,6 +57,7 @@
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ClockSectionTest : SysuiTestCase() {
@@ -127,7 +128,7 @@
     fun testApplyDefaultConstraints_LargeClock_SplitShade() =
         kosmos.testScope.runTest {
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
                 advanceUntilIdle()
             }
@@ -143,11 +144,11 @@
     fun testApplyDefaultConstraints_LargeClock_NonSplitShade() =
         kosmos.testScope.runTest {
             with(kosmos) {
-                val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+                val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
                 val isLargeClockVisible by
                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
 
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
                 fakeKeyguardRepository.setClockShouldBeCentered(true)
                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -168,11 +169,11 @@
         kosmos.testScope.runTest {
             with(kosmos) {
                 DIMENSION_BY_IDENTIFIER = listOf() // Remove Smartspace from mock
-                val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+                val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
                 val isLargeClockVisible by
                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
 
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
                 fakeKeyguardRepository.setClockShouldBeCentered(true)
                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -193,11 +194,11 @@
         kosmos.testScope.runTest {
             with(kosmos) {
                 DIMENSION_BY_IDENTIFIER = listOf() // Remove Smartspace from mock
-                val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+                val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
                 val isLargeClockVisible by
                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
 
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
                 fakeKeyguardRepository.setClockShouldBeCentered(true)
                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -217,11 +218,11 @@
     fun testApplyDefaultConstraints_SmallClock_SplitShade() =
         kosmos.testScope.runTest {
             with(kosmos) {
-                val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+                val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
                 val isLargeClockVisible by
                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
 
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 keyguardClockInteractor.setClockSize(ClockSize.SMALL)
                 fakeKeyguardRepository.setClockShouldBeCentered(true)
                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -241,11 +242,11 @@
     fun testApplyDefaultConstraints_SmallClock_NonSplitShade() =
         kosmos.testScope.runTest {
             with(kosmos) {
-                val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+                val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
                 val isLargeClockVisible by
                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
 
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 keyguardClockInteractor.setClockSize(ClockSize.SMALL)
                 fakeKeyguardRepository.setClockShouldBeCentered(true)
                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -334,7 +335,7 @@
     }
 
     companion object {
-        private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
-        private val ENHANCED_SMART_SPACE_HEIGHT = 11
+        private const val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
+        private const val ENHANCED_SMART_SPACE_HEIGHT = 11
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 40663ce..17e1b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
@@ -88,7 +87,7 @@
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
 
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 keyguardRepository.setClockShouldBeCentered(true)
                 keyguardClockRepository.setClockSize(ClockSize.LARGE)
             }
@@ -103,7 +102,7 @@
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
 
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 keyguardRepository.setClockShouldBeCentered(false)
                 keyguardClockRepository.setClockSize(ClockSize.LARGE)
             }
@@ -118,7 +117,7 @@
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
 
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 keyguardRepository.setClockShouldBeCentered(false)
                 keyguardClockRepository.setClockSize(ClockSize.SMALL)
             }
@@ -133,7 +132,7 @@
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
 
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 keyguardClockRepository.setClockSize(ClockSize.SMALL)
             }
 
@@ -146,7 +145,7 @@
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
 
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 keyguardClockRepository.setClockSize(ClockSize.LARGE)
             }
 
@@ -234,7 +233,7 @@
     fun testSmallClockTop_splitShade_composeLockscreenOn() =
         testScope.runTest {
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
             }
 
@@ -249,7 +248,7 @@
     fun testSmallClockTop_splitShade_composeLockscreenOff() =
         testScope.runTest {
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Split)
+                shadeRepository.setShadeLayoutWide(true)
                 fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
             }
 
@@ -262,7 +261,7 @@
     fun testSmallClockTop_nonSplitShade_composeLockscreenOn() =
         testScope.runTest {
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
             }
 
@@ -275,7 +274,7 @@
     fun testSmallClockTop_nonSplitShade_composeLockscreenOff() =
         testScope.runTest {
             with(kosmos) {
-                shadeRepository.setShadeMode(ShadeMode.Single)
+                shadeRepository.setShadeLayoutWide(false)
                 fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
             }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index c0d411b..c604b6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.mediaprojection.data.repository
 
+import android.hardware.display.displayManager
 import android.media.projection.MediaProjectionInfo
 import android.os.Binder
 import android.os.UserHandle
 import android.view.ContentRecordingSession
+import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,7 +38,9 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -47,6 +51,7 @@
 
     private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
     private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+    private val displayManager = kosmos.displayManager
 
     private val repo = kosmos.realMediaProjectionRepository
 
@@ -139,6 +144,37 @@
         }
 
     @Test
+    fun mediaProjectionState_entireScreen_validVirtualDisplayId_hasHostDeviceName() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+            session.virtualDisplayId = 45
+            val displayInfo = mock<Display>().apply { whenever(this.name).thenReturn("Test Name") }
+            whenever(displayManager.getDisplay(45)).thenReturn(displayInfo)
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+            assertThat((state as MediaProjectionState.Projecting.EntireScreen).hostDeviceName)
+                .isEqualTo("Test Name")
+        }
+
+    @Test
+    fun mediaProjectionState_entireScreen_invalidVirtualDisplayId_nullHostDeviceName() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+            session.virtualDisplayId = 45
+            whenever(displayManager.getDisplay(45)).thenReturn(null)
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+            assertThat((state as MediaProjectionState.Projecting.EntireScreen).hostDeviceName)
+                .isNull()
+        }
+
+    @Test
     fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
         testScope.runTest {
             val token = createToken()
@@ -179,6 +215,45 @@
         }
 
     @Test
+    fun mediaProjectionState_singleTask_validVirtualDisplayId_hasHostDeviceName() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            val token = createToken()
+            val task = createTask(taskId = 1, token = token)
+            fakeActivityTaskManager.addRunningTasks(task)
+
+            val session = ContentRecordingSession.createTaskSession(token.asBinder())
+            session.virtualDisplayId = 45
+            val displayInfo = mock<Display>().apply { whenever(this.name).thenReturn("Test Name") }
+            whenever(displayManager.getDisplay(45)).thenReturn(displayInfo)
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+            assertThat((state as MediaProjectionState.Projecting.SingleTask).hostDeviceName)
+                .isEqualTo("Test Name")
+        }
+
+    @Test
+    fun mediaProjectionState_singleTask_invalidVirtualDisplayId_nullHostDeviceName() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            val token = createToken()
+            val task = createTask(taskId = 1, token = token)
+            fakeActivityTaskManager.addRunningTasks(task)
+
+            val session = ContentRecordingSession.createTaskSession(token.asBinder())
+            session.virtualDisplayId = 45
+            whenever(displayManager.getDisplay(45)).thenReturn(null)
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+            assertThat((state as MediaProjectionState.Projecting.SingleTask).hostDeviceName)
+                .isNull()
+        }
+
+    @Test
     fun stopProjecting_invokesManager() =
         testScope.runTest {
             repo.stopProjecting()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
index b0265c0..3621ab9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
@@ -103,7 +103,7 @@
     public void setIsLightsOut_AutoDim() {
         mTransitions.setAutoDim(true);
 
-        assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT));
+        assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_OPAQUE));
 
         assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 798e9fb..d6bde27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.service.quicksettings.Tile;
 import android.testing.TestableLooper;
 
@@ -32,6 +33,7 @@
 
 import com.android.internal.R;
 import com.android.internal.logging.MetricsLogger;
+import com.android.server.display.feature.flags.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -131,6 +133,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
     public void testActive_clicked_featureIsActivated() {
         when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false);
         mTile.refreshState();
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 0b81b5e..8d3a29a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -69,7 +69,7 @@
 
     @Before
     fun setUp() {
-        whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+        whenever(controllerFactory.create(any())).thenReturn(controller)
         whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
         whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
     }
@@ -83,8 +83,8 @@
             val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
-            verify(controllerFactory).create(eq(internalDisplay), any())
-            verify(controllerFactory, never()).create(eq(externalDisplay), any())
+            verify(controllerFactory).create(eq(internalDisplay))
+            verify(controllerFactory, never()).create(eq(externalDisplay))
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -118,8 +118,8 @@
                 callback
             )
 
-            verify(controllerFactory).create(eq(internalDisplay), any())
-            verify(controllerFactory, never()).create(eq(externalDisplay), any())
+            verify(controllerFactory).create(eq(internalDisplay))
+            verify(controllerFactory, never()).create(eq(externalDisplay))
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -151,7 +151,7 @@
     @Test
     fun executeScreenshots_allowedTypes_allCaptured() =
         testScope.runTest {
-            whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+            whenever(controllerFactory.create(any())).thenReturn(controller)
 
             setDisplays(
                 display(TYPE_INTERNAL, id = 0),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index baf1357..193d29c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -334,13 +334,17 @@
     }
 
     private void resetPackageManagerMockingForUsingFallbackBacklinks() {
+        ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo();
         reset(mPackageManager);
         when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
         when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
-                // First the logic queries whether a package has a launcher activity, this should
+                // Firstly, the logic queries whether a package has a launcher activity, this should
                 // resolve otherwise the logic filters out the task.
-                .thenReturn(createBacklinksTaskResolveInfo())
-                // Then logic queries with the backlinks intent, this should not resolve for the
+                .thenReturn(backlinksTaskResolveInfo)
+                // Secondly, the logic builds a fallback main launcher intent, this should also
+                // resolve for the fallback intent to build correctly.
+                .thenReturn(backlinksTaskResolveInfo)
+                // Lastly, logic queries with the backlinks intent, this should not resolve for the
                 // logic to use the fallback intent.
                 .thenReturn(null);
     }
@@ -360,6 +364,8 @@
         assertThat(actualBacklinksIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME);
         assertThat(actualBacklinksIntent.getAction()).isEqualTo(ACTION_MAIN);
         assertThat(actualBacklinksIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
+        assertThat(actualBacklinksIntent.getComponent()).isEqualTo(
+                new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, BACKLINKS_TASK_APP_NAME));
     }
 
     private static ResolveInfo createBacklinksTaskResolveInfo() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 97acc6e..7d4918a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 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.runTest
@@ -28,7 +28,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadeRepositoryImplTest : SysuiTestCase() {
@@ -91,37 +90,37 @@
     @Test
     fun updateLegacyShadeTracking() =
         testScope.runTest {
-            assertThat(underTest.legacyShadeTracking.value).isEqualTo(false)
+            assertThat(underTest.legacyShadeTracking.value).isFalse()
 
             underTest.setLegacyShadeTracking(true)
-            assertThat(underTest.legacyShadeTracking.value).isEqualTo(true)
+            assertThat(underTest.legacyShadeTracking.value).isTrue()
         }
 
     @Test
     fun updateLegacyLockscreenShadeTracking() =
         testScope.runTest {
-            assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(false)
+            assertThat(underTest.legacyLockscreenShadeTracking.value).isFalse()
 
             underTest.setLegacyLockscreenShadeTracking(true)
-            assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(true)
+            assertThat(underTest.legacyLockscreenShadeTracking.value).isTrue()
         }
 
     @Test
     fun updateLegacyQsTracking() =
         testScope.runTest {
-            assertThat(underTest.legacyQsTracking.value).isEqualTo(false)
+            assertThat(underTest.legacyQsTracking.value).isFalse()
 
             underTest.setLegacyQsTracking(true)
-            assertThat(underTest.legacyQsTracking.value).isEqualTo(true)
+            assertThat(underTest.legacyQsTracking.value).isTrue()
         }
 
     @Test
     fun updateLegacyExpandedOrAwaitingInputTransfer() =
         testScope.runTest {
-            assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isEqualTo(false)
+            assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isFalse()
 
             underTest.setLegacyExpandedOrAwaitingInputTransfer(true)
-            assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isEqualTo(true)
+            assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isTrue()
         }
 
     @Test
@@ -142,36 +141,46 @@
     @Test
     fun updateLegacyIsQsExpanded() =
         testScope.runTest {
-            assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(false)
+            assertThat(underTest.legacyIsQsExpanded.value).isFalse()
 
             underTest.setLegacyIsQsExpanded(true)
-            assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(true)
+            assertThat(underTest.legacyIsQsExpanded.value).isTrue()
         }
 
     @Test
     fun updateLegacyExpandImmediate() =
         testScope.runTest {
-            assertThat(underTest.legacyExpandImmediate.value).isEqualTo(false)
+            assertThat(underTest.legacyExpandImmediate.value).isFalse()
 
             underTest.setLegacyExpandImmediate(true)
-            assertThat(underTest.legacyExpandImmediate.value).isEqualTo(true)
+            assertThat(underTest.legacyExpandImmediate.value).isTrue()
         }
 
     @Test
     fun updateLegacyQsFullscreen() =
         testScope.runTest {
-            assertThat(underTest.legacyQsFullscreen.value).isEqualTo(false)
+            assertThat(underTest.legacyQsFullscreen.value).isFalse()
 
             underTest.setLegacyQsFullscreen(true)
-            assertThat(underTest.legacyQsFullscreen.value).isEqualTo(true)
+            assertThat(underTest.legacyQsFullscreen.value).isTrue()
         }
 
     @Test
     fun updateLegacyIsClosing() =
         testScope.runTest {
-            assertThat(underTest.legacyIsClosing.value).isEqualTo(false)
+            assertThat(underTest.legacyIsClosing.value).isFalse()
 
             underTest.setLegacyIsClosing(true)
-            assertThat(underTest.legacyIsClosing.value).isEqualTo(true)
+            assertThat(underTest.legacyIsClosing.value).isTrue()
+        }
+
+    @Test
+    fun isShadeLayoutWide() =
+        testScope.runTest {
+            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+            assertThat(isShadeLayoutWide).isFalse()
+
+            underTest.setShadeLayoutWide(true)
+            assertThat(isShadeLayoutWide).isTrue()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index c8397bd..5005d16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -75,7 +75,9 @@
 
     @Test
     fun message_entireScreen_unknownDevice() {
-        createAndSetDelegate(ENTIRE_SCREEN, deviceName = null)
+        createAndSetDelegate(
+            MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE, hostDeviceName = null)
+        )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
@@ -87,7 +89,12 @@
 
     @Test
     fun message_entireScreen_hasDevice() {
-        createAndSetDelegate(ENTIRE_SCREEN, deviceName = "My Favorite Device")
+        createAndSetDelegate(
+            MediaProjectionState.Projecting.EntireScreen(
+                HOST_PACKAGE,
+                hostDeviceName = "My Favorite Device"
+            )
+        )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
@@ -110,9 +117,9 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
+                hostDeviceName = null,
                 createTask(taskId = 1, baseIntent = baseIntent)
             ),
-            deviceName = null,
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -133,9 +140,9 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
+                hostDeviceName = "My Favorite Device",
                 createTask(taskId = 1, baseIntent = baseIntent)
             ),
-            deviceName = "My Favorite Device",
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -161,9 +168,9 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
+                hostDeviceName = null,
                 createTask(taskId = 1, baseIntent = baseIntent)
             ),
-            deviceName = null,
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -189,9 +196,9 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
-                createTask(taskId = 1, baseIntent = baseIntent)
+                hostDeviceName = "My Favorite Device",
+                createTask(taskId = 1, baseIntent = baseIntent),
             ),
-            deviceName = "My Favorite Device",
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -240,10 +247,7 @@
             assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
         }
 
-    private fun createAndSetDelegate(
-        state: MediaProjectionState.Projecting,
-        deviceName: String? = null,
-    ) {
+    private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
         underTest =
             EndCastScreenToOtherDeviceDialogDelegate(
                 kosmos.endMediaProjectionDialogHelper,
@@ -252,15 +256,19 @@
                 ProjectionChipModel.Projecting(
                     ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE,
                     state,
-                    deviceName,
                 ),
             )
     }
 
     companion object {
         private const val HOST_PACKAGE = "fake.host.package"
-        private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
+        private val ENTIRE_SCREEN =
+            MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE, hostDeviceName = null)
         private val SINGLE_TASK =
-            MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+            MediaProjectionState.Projecting.SingleTask(
+                HOST_PACKAGE,
+                hostDeviceName = null,
+                createTask(taskId = 1)
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index fe29140..c9a7c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -38,7 +37,6 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.policy.CastDevice
@@ -47,9 +45,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -65,17 +61,6 @@
     private val mockScreenCastDialog = mock<SystemUIDialog>()
     private val mockGenericCastDialog = mock<SystemUIDialog>()
 
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
-
     private val underTest = kosmos.castToOtherDeviceChipViewModel
 
     @Before
@@ -116,6 +101,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
@@ -223,7 +209,11 @@
             val latest by collectLastValue(underTest.chip)
 
             mediaProjectionRepo.mediaProjectionState.value =
-                MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1))
+                MediaProjectionState.Projecting.SingleTask(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
@@ -258,6 +248,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
@@ -306,14 +297,8 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockScreenCastDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockScreenCastDialog).show()
         }
 
     @Test
@@ -324,20 +309,15 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockScreenCastDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockScreenCastDialog).show()
         }
 
     @Test
@@ -359,13 +339,7 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockGenericCastDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockGenericCastDialog).show()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index 7eb4ca0..d0c5e7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -72,6 +72,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1)
                 )
 
@@ -101,7 +102,11 @@
             val latest by collectLastValue(underTest.projection)
 
             mediaProjectionRepo.mediaProjectionState.value =
-                MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1))
+                MediaProjectionState.Projecting.SingleTask(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
 
             assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
             assertThat((latest as ProjectionChipModel.Projecting).type)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
index ab935fe..c62e404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -72,6 +72,7 @@
         val projectionState =
             MediaProjectionState.Projecting.SingleTask(
                 "host.package",
+                hostDeviceName = null,
                 createTask(taskId = 1, baseIntent = baseIntent),
             )
 
@@ -92,6 +93,7 @@
         val projectionState =
             MediaProjectionState.Projecting.SingleTask(
                 "host.package",
+                hostDeviceName = null,
                 createTask(taskId = 1, baseIntent = baseIntent),
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
index 83b31c2..6bfb40f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
@@ -103,7 +103,11 @@
             screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
             val task = createTask(taskId = 1)
             mediaProjectionRepo.mediaProjectionState.value =
-                MediaProjectionState.Projecting.SingleTask("host.package", task)
+                MediaProjectionState.Projecting.SingleTask(
+                    "host.package",
+                    hostDeviceName = null,
+                    task,
+                )
 
             assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 0a06cc7..4728c64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -34,7 +33,6 @@
 import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -42,9 +40,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -58,17 +54,6 @@
     private val systemClock = kosmos.fakeSystemClock
     private val mockSystemUIDialog = mock<SystemUIDialog>()
 
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
-
     private val underTest = kosmos.screenRecordChipViewModel
 
     @Before
@@ -197,15 +182,9 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
+            clickListener!!.onClick(mock<View>())
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            verify(mockSystemUIDialog).show()
         }
 
     @Test
@@ -219,15 +198,9 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
+            clickListener!!.onClick(mock<View>())
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            verify(mockSystemUIDialog).show()
         }
 
     @Test
@@ -238,20 +211,15 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     "host.package",
+                    hostDeviceName = null,
                     FakeActivityTaskManager.createTask(taskId = 1)
                 )
 
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
+            clickListener!!.onClick(mock<View>())
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            verify(mockSystemUIDialog).show()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index bfb57c5..325a42b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -116,6 +116,7 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
+                hostDeviceName = null,
                 createTask(taskId = 1, baseIntent = baseIntent)
             )
         )
@@ -140,6 +141,7 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
+                hostDeviceName = null,
                 createTask(taskId = 1, baseIntent = baseIntent)
             )
         )
@@ -200,7 +202,6 @@
                 ProjectionChipModel.Projecting(
                     ProjectionChipModel.Type.SHARE_TO_APP,
                     state,
-                    deviceName = null,
                 ),
             )
     }
@@ -209,6 +210,10 @@
         private const val HOST_PACKAGE = "fake.host.package"
         private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
         private val SINGLE_TASK =
-            MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+            MediaProjectionState.Projecting.SingleTask(
+                HOST_PACKAGE,
+                hostDeviceName = null,
+                createTask(taskId = 1)
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 3028d00..f87b17d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -35,7 +34,6 @@
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -43,9 +41,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -59,17 +55,6 @@
 
     private val mockShareDialog = mock<SystemUIDialog>()
 
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
-
     private val underTest = kosmos.shareToAppChipViewModel
 
     @Before
@@ -98,6 +83,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
@@ -123,6 +109,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     NORMAL_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
@@ -176,6 +163,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     NORMAL_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
@@ -193,14 +181,8 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockShareDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockShareDialog).show()
         }
 
     @Test
@@ -210,19 +192,14 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     NORMAL_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockShareDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockShareDialog).show()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index c9c7359..2e0c773 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -19,50 +19,25 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlin.test.Test
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
 
 @SmallTest
 class OngoingActivityChipViewModelTest : SysuiTestCase() {
     private val mockSystemUIDialog = mock<SystemUIDialog>()
     private val dialogDelegate = SystemUIDialog.Delegate { mockSystemUIDialog }
-    private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
-
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
 
     @Test
     fun createDialogLaunchOnClickListener_showsDialogOnClick() {
-        val clickListener =
-            createDialogLaunchOnClickListener(dialogDelegate, dialogTransitionAnimator)
+        val clickListener = createDialogLaunchOnClickListener(dialogDelegate)
 
         // Dialogs must be created on the main thread
         context.mainExecutor.execute {
-            clickListener.onClick(chipView)
-            verify(dialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    anyBoolean(),
-                )
+            clickListener.onClick(mock<View>())
+            verify(mockSystemUIDialog).show()
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 8bc83cf..b1a8d0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -155,6 +155,7 @@
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.SingleTask(
                     NORMAL_PACKAGE,
+                    hostDeviceName = null,
                     createTask(taskId = 1),
                 )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index e8349b0..ed7383c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -1003,21 +1003,6 @@
         // THEN
         assertThat(row.isExpanded()).isFalse();
     }
-
-    @Test
-    @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
-    public void hasUserChangedExpansion_expandPinned_returnTrue() throws Exception {
-        // GIVEN
-        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPinned(true);
-
-        // WHEN
-        row.expandNotification();
-
-        // THEN
-        assertThat(row.hasUserChangedExpansion()).isTrue();
-    }
-
     @Test
     public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
             throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 30e96f1..e439aff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -738,6 +738,28 @@
             assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
         }
 
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    // See b/346904529 for more context
+    fun satBasedIcon_doesNotInflateSignalStrength() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            // GIVEN a satellite connection
+            connectionRepository.isNonTerrestrial.value = true
+            // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+            connectionRepository.inflateSignalStrength.value = true
+
+            connectionRepository.primaryLevel.value = 4
+            assertThat(latest!!.level).isEqualTo(4)
+
+            connectionRepository.inflateSignalStrength.value = true
+            connectionRepository.primaryLevel.value = 4
+
+            // Icon level is unaffected
+            assertThat(latest!!.level).isEqualTo(4)
+        }
+
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index cec4155..e510924 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -862,6 +862,38 @@
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
         }
 
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun satelliteIcon_ignoresInflateSignalStrength() =
+        testScope.runTest {
+            // Note that this is the exact same test as above, but with inflateSignalStrength set to
+            // true we note that the level is unaffected by inflation
+            repository.inflateSignalStrength.value = true
+            repository.isNonTerrestrial.value = true
+            repository.setAllLevels(0)
+
+            val latest by
+                collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+            // Level 0 -> no connection
+            assertThat(latest).isNotNull()
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+            // 1-2 -> 1 bar
+            repository.setAllLevels(1)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            repository.setAllLevels(2)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            // 3-4 -> 2 bars
+            repository.setAllLevels(3)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+            repository.setAllLevels(4)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+        }
+
     private fun createAndSetViewModel() {
         underTest =
             MobileIconViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index cf0db7b..ce6bc09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -29,9 +29,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@Ignore
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class BackGestureMonitorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index 769f264..f5ef8b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -34,9 +34,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@Ignore
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TouchpadGestureHandlerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java b/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java
deleted file mode 100644
index bb7b31b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2017 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.tuner;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.LeakCheck.Tracker;
-import android.util.DisplayMetrics;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TunablePaddingTest extends LeakCheckedTest {
-
-    private static final String KEY = "KEY";
-    private static final int DEFAULT = 42;
-    private View mView;
-    private TunablePadding mTunablePadding;
-    private TunerService mTunerService;
-
-    @Before
-    public void setup() {
-        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
-        mView = mock(View.class);
-        when(mView.getContext()).thenReturn(mContext);
-
-        mTunerService = mock(TunerService.class);
-        mDependency.injectTestDependency(TunablePadding.TunablePaddingService.class,
-                new TunablePadding.TunablePaddingService(mTunerService));
-        Tracker tracker = mLeakCheck.getTracker("tuner");
-        doAnswer(invocation -> {
-            tracker.getLeakInfo(invocation.getArguments()[0]).addAllocation(new Throwable());
-            return null;
-        }).when(mTunerService).addTunable(any(), any());
-        doAnswer(invocation -> {
-            tracker.getLeakInfo(invocation.getArguments()[0]).clearAllocations();
-            return null;
-        }).when(mTunerService).removeTunable(any());
-    }
-
-    @Test
-    public void testFlags() {
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_START);
-        mTunablePadding.onTuningChanged(null, null);
-        verify(mView).setPadding(eq(DEFAULT), eq(0), eq(0), eq(0));
-        mTunablePadding.destroy();
-
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_TOP);
-        mTunablePadding.onTuningChanged(null, null);
-        verify(mView).setPadding(eq(0), eq(DEFAULT), eq(0), eq(0));
-        mTunablePadding.destroy();
-
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_END);
-        mTunablePadding.onTuningChanged(null, null);
-        verify(mView).setPadding(eq(0), eq(0), eq(DEFAULT), eq(0));
-        mTunablePadding.destroy();
-
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_BOTTOM);
-        mTunablePadding.onTuningChanged(null, null);
-        verify(mView).setPadding(eq(0), eq(0), eq(0), eq(DEFAULT));
-        mTunablePadding.destroy();
-    }
-
-    @Test
-    public void testRtl() {
-        when(mView.isLayoutRtl()).thenReturn(true);
-
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_END);
-        mTunablePadding.onTuningChanged(null, null);
-        verify(mView).setPadding(eq(DEFAULT), eq(0), eq(0), eq(0));
-        mTunablePadding.destroy();
-
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_START);
-        mTunablePadding.onTuningChanged(null, null);
-        verify(mView).setPadding(eq(0), eq(0), eq(DEFAULT), eq(0));
-        mTunablePadding.destroy();
-    }
-
-    @Test
-    public void testTuning() {
-        int value = 3;
-        mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
-                TunablePadding.FLAG_START);
-        mTunablePadding.onTuningChanged(KEY, String.valueOf(value));
-
-        DisplayMetrics metrics = new DisplayMetrics();
-        mContext.getSystemService(WindowManager.class).getDefaultDisplay().getMetrics(metrics);
-        int output = (int) (metrics.density * value);
-        verify(mView).setPadding(eq(output), eq(0), eq(0), eq(0));
-
-        mTunablePadding.destroy();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index dd791e7..5ac6110 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -28,9 +28,10 @@
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Before
@@ -44,6 +45,7 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 @TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
 class SettingsProxyTest : SysuiTestCase() {
 
     private val testDispatcher = StandardTestDispatcher()
@@ -60,11 +62,12 @@
     }
 
     @Test
-    fun registerContentObserver_inputString_success() {
-        mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver)
-        verify(mSettings.getContentResolver())
-            .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
-    }
+    fun registerContentObserver_inputString_success() =
+        testScope.runTest {
+            mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver)
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+        }
 
     @Test
     fun registerContentObserverSuspend_inputString_success() =
@@ -75,24 +78,25 @@
         }
 
     @Test
-    fun registerContentObserverAsync_inputString_success() {
-        mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver)
-        testScope.launch {
+    fun registerContentObserverAsync_inputString_success() =
+        testScope.runTest {
+            mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver)
+            testScope.advanceUntilIdle()
             verify(mSettings.getContentResolver())
                 .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
         }
-    }
 
     @Test
-    fun registerContentObserver_inputString_notifyForDescendants_true() {
-        mSettings.registerContentObserverSync(
-            TEST_SETTING,
-            notifyForDescendants = true,
-            mContentObserver
-        )
-        verify(mSettings.getContentResolver())
-            .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
-    }
+    fun registerContentObserver_inputString_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserverSync(
+                TEST_SETTING,
+                notifyForDescendants = true,
+                mContentObserver
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+        }
 
     @Test
     fun registerContentObserverSuspend_inputString_notifyForDescendants_true() =
@@ -107,24 +111,25 @@
         }
 
     @Test
-    fun registerContentObserverAsync_inputString_notifyForDescendants_true() {
-        mSettings.registerContentObserverAsync(
-            TEST_SETTING,
-            notifyForDescendants = true,
-            mContentObserver
-        )
-        testScope.launch {
+    fun registerContentObserverAsync_inputString_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserverAsync(
+                TEST_SETTING,
+                notifyForDescendants = true,
+                mContentObserver
+            )
+            testScope.advanceUntilIdle()
             verify(mSettings.getContentResolver())
                 .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
         }
-    }
 
     @Test
-    fun registerContentObserver_inputUri_success() {
-        mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
-        verify(mSettings.getContentResolver())
-            .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
-    }
+    fun registerContentObserver_inputUri_success() =
+        testScope.runTest {
+            mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+        }
 
     @Test
     fun registerContentObserverSuspend_inputUri_success() =
@@ -135,24 +140,25 @@
         }
 
     @Test
-    fun registerContentObserverAsync_inputUri_success() {
-        mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
-        testScope.launch {
+    fun registerContentObserverAsync_inputUri_success() =
+        testScope.runTest {
+            mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+            testScope.advanceUntilIdle()
             verify(mSettings.getContentResolver())
                 .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
         }
-    }
 
     @Test
-    fun registerContentObserver_inputUri_notifyForDescendants_true() {
-        mSettings.registerContentObserverSync(
-            TEST_SETTING_URI,
-            notifyForDescendants = true,
-            mContentObserver
-        )
-        verify(mSettings.getContentResolver())
-            .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
-    }
+    fun registerContentObserver_inputUri_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserverSync(
+                TEST_SETTING_URI,
+                notifyForDescendants = true,
+                mContentObserver
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+        }
 
     @Test
     fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
@@ -167,23 +173,56 @@
         }
 
     @Test
-    fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
-        mSettings.registerContentObserverAsync(
-            TEST_SETTING_URI,
-            notifyForDescendants = true,
-            mContentObserver
-        )
-        testScope.launch {
+    fun registerContentObserverAsync_inputUri_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserverAsync(
+                TEST_SETTING_URI,
+                notifyForDescendants = true,
+                mContentObserver
+            )
+            testScope.advanceUntilIdle()
             verify(mSettings.getContentResolver())
                 .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
         }
+
+    @Test
+    fun registerContentObserverAsync_registeredLambdaPassed_callsCallback() =
+        testScope.runTest {
+            verifyRegisteredCallbackForRegistration {
+                mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver, it)
+            }
+            verifyRegisteredCallbackForRegistration {
+                mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver, it)
+            }
+            verifyRegisteredCallbackForRegistration {
+                mSettings.registerContentObserverAsync(TEST_SETTING, false, mContentObserver, it)
+            }
+            verifyRegisteredCallbackForRegistration {
+                mSettings.registerContentObserverAsync(
+                    TEST_SETTING_URI,
+                    false,
+                    mContentObserver,
+                    it
+                )
+            }
+        }
+
+    private fun verifyRegisteredCallbackForRegistration(
+        call: (registeredRunnable: Runnable) -> Unit
+    ) {
+        var callbackCalled = false
+        val runnable = { callbackCalled = true }
+        call(runnable)
+        testScope.advanceUntilIdle()
+        assertThat(callbackCalled).isTrue()
     }
 
     @Test
-    fun unregisterContentObserverSync() {
-        mSettings.unregisterContentObserverSync(mContentObserver)
-        verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
-    }
+    fun unregisterContentObserverSync() =
+        testScope.runTest {
+            mSettings.unregisterContentObserverSync(mContentObserver)
+            verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
+        }
 
     @Test
     fun unregisterContentObserverSuspend_inputString_success() =
@@ -193,12 +232,12 @@
         }
 
     @Test
-    fun unregisterContentObserverAsync_inputString_success() {
-        mSettings.unregisterContentObserverAsync(mContentObserver)
-        testScope.launch {
+    fun unregisterContentObserverAsync_inputString_success() =
+        testScope.runTest {
+            mSettings.unregisterContentObserverAsync(mContentObserver)
+            testScope.advanceUntilIdle()
             verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
         }
-    }
 
     @Test
     fun getString_keyPresent_returnValidValue() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt
index e40152a..796ec94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.data.repository
+package android.hardware.display
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+val Kosmos.displayManager by Kosmos.Fixture { mock<DisplayManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
index 8b0affe2..e02042d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility
 
+import android.content.res.Resources
+import com.android.server.display.feature.flags.Flags
 import com.android.systemui.qs.ReduceBrightColorsController
 
 class FakeReduceBrightColorsController : ReduceBrightColorsController {
@@ -44,4 +46,20 @@
             }
         }
     }
+
+    override fun setReduceBrightColorsFeatureAvailable(enabled: Boolean) {
+        // do nothing
+    }
+
+    override fun isReduceBrightColorsFeatureAvailable(): Boolean {
+        return true
+    }
+
+    override fun isInUpgradeMode(resources: Resources?): Boolean {
+        if (resources != null) {
+            return Flags.evenDimmer() &&
+                resources.getBoolean(com.android.internal.R.bool.config_evenDimmerEnabled)
+        }
+        return false
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt
index e40152a..559a6ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.data.repository
+package com.android.systemui.communal.data.repository
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+val Kosmos.fakeCommunalSmartspaceRepository by Kosmos.Fixture { FakeCommunalSmartspaceRepository() }
+
+val Kosmos.communalSmartspaceRepository by
+    Kosmos.Fixture<CommunalSmartspaceRepository> { fakeCommunalSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 1884a32..14b1984 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -36,4 +36,18 @@
     fun mediaInactive() {
         _mediaModel.value = CommunalMediaModel.INACTIVE
     }
+
+    private var isListening = false
+
+    override fun startListening() {
+        isListening = true
+    }
+
+    override fun stopListening() {
+        isListening = false
+    }
+
+    fun isListening(): Boolean {
+        return isListening
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt
new file mode 100644
index 0000000..904ab4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.communal.data.repository
+
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeCommunalSmartspaceRepository : CommunalSmartspaceRepository {
+
+    private val _timers = MutableStateFlow<List<CommunalSmartspaceTimer>>(emptyList())
+    override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers
+
+    fun setTimers(timers: List<CommunalSmartspaceTimer>) {
+        _timers.value = timers
+    }
+
+    private var isListening = false
+
+    override fun startListening() {
+        isListening = true
+    }
+
+    override fun stopListening() {
+        isListening = false
+    }
+
+    fun isListening(): Boolean {
+        return isListening
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index b58861b..eb92785 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import android.os.userManager
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalSmartspaceRepository
 import com.android.systemui.communal.data.repository.communalWidgetRepository
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.flags.Flags
@@ -34,7 +35,6 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.settings.userTracker
-import com.android.systemui.smartspace.data.repository.smartspaceRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
 
@@ -47,7 +47,7 @@
         widgetRepository = communalWidgetRepository,
         communalPrefsInteractor = communalPrefsInteractor,
         mediaRepository = communalMediaRepository,
-        smartspaceRepository = smartspaceRepository,
+        smartspaceRepository = communalSmartspaceRepository,
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         communalSettingsInteractor = communalSettingsInteractor,
@@ -64,13 +64,17 @@
 
 val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() }
 
-suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
-    fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, available)
-    if (available) {
+suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
+    fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
+    if (enabled) {
         fakeUserRepository.asMainUser()
     } else {
         fakeUserRepository.asDefaultUser()
     }
+}
+
+suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
+    setCommunalEnabled(available)
     with(fakeKeyguardRepository) {
         setIsEncryptedOrLockdown(!available)
         setKeyguardShowing(available)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index eff99e04..28355e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.haptics.qs
 
+import com.android.systemui.classifier.falsingManager
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.policy.keyguardStateController
 
 val Kosmos.qsLongPressEffect by
-    Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController) }
+    Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController, falsingManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index f436a68..530df8a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
 import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
 import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
@@ -70,6 +71,9 @@
 val Kosmos.shortcutHelperInputShortcutsSource by
     Kosmos.Fixture { InputShortcutsSource(mainResources, windowManager) }
 
+val Kosmos.shortcutHelperCurrentAppShortcutsSource by
+    Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
+
 val Kosmos.shortcutHelperCategoriesRepository by
     Kosmos.Fixture {
         ShortcutHelperCategoriesRepository(
@@ -79,6 +83,7 @@
             shortcutHelperMultiTaskingShortcutsSource,
             shortcutHelperAppCategoriesShortcutsSource,
             shortcutHelperInputShortcutsSource,
+            shortcutHelperCurrentAppShortcutsSource,
             fakeInputManager.inputManager,
             shortcutHelperStateRepository,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
index 4328ca1..406b5cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -16,11 +16,9 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.content.applicationContext
 import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
-import com.android.systemui.keyguard.data.repository.keyguardClockSection
 import com.android.systemui.keyguard.data.repository.keyguardSmartspaceSection
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -31,12 +29,9 @@
         KeyguardBlueprintInteractor(
             keyguardBlueprintRepository = keyguardBlueprintRepository,
             applicationScope = applicationCoroutineScope,
-            context = applicationContext,
             shadeInteractor = shadeInteractor,
-            clockInteractor = keyguardClockInteractor,
             configurationInteractor = configurationInteractor,
             fingerprintPropertyInteractor = fingerprintPropertyInteractor,
-            clockSection = keyguardClockSection,
             smartspaceSection = keyguardSmartspaceSection,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 03e5a90..2c6d44f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
@@ -30,6 +29,5 @@
             transitionInteractor = keyguardTransitionInteractor,
             dismissInteractor = keyguardDismissInteractor,
             applicationScope = testScope.backgroundScope,
-            sceneInteractor = sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
index f253e94..81ba77a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediaprojection.data.repository
 
+import android.hardware.display.displayManager
 import android.os.Handler
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -30,6 +31,7 @@
     Kosmos.Fixture {
         MediaProjectionManagerRepository(
             mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            displayManager = displayManager,
             handler = Handler.getMain(),
             applicationScope = applicationCoroutineScope,
             tasksRepository = activityTaskManagerTasksRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepositoryKosmos.kt
similarity index 84%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepositoryKosmos.kt
index e40152a..92ac089 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepositoryKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.defaultLargeTilesRepository: DefaultLargeTilesRepository by
+    Kosmos.Fixture { DefaultLargeTilesRepositoryImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
index 39ae5eb..513d4e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
@@ -22,4 +22,11 @@
 import com.android.systemui.user.data.repository.userRepository
 
 val Kosmos.qsPreferencesRepository by
-    Kosmos.Fixture { QSPreferencesRepository(userFileManager, userRepository, testDispatcher) }
+    Kosmos.Fixture {
+        QSPreferencesRepository(
+            userFileManager,
+            userRepository,
+            defaultLargeTilesRepository,
+            testDispatcher
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index eaa702f..76dccdb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -17,6 +17,16 @@
 package com.android.systemui.qs.panels.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
 
-val Kosmos.iconTilesInteractor by Kosmos.Fixture { IconTilesInteractor(iconTilesRepository) }
+val Kosmos.iconTilesInteractor by
+    Kosmos.Fixture {
+        IconTilesInteractor(
+            defaultLargeTilesRepository,
+            qsPreferencesInteractor,
+            FakeLogBuffer.Factory.create(),
+            applicationCoroutineScope
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index b9918f1..4660337 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -30,53 +29,65 @@
 @SysUISingleton
 class FakeShadeRepository @Inject constructor() : ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
-    @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion = _qsExpansion
+
+    @Deprecated("Use ShadeInteractor.qsExpansion instead")
+    override val qsExpansion = _qsExpansion.asStateFlow()
 
     private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
-    override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
+    override val udfpsTransitionToFullShadeProgress =
+        _udfpsTransitionToFullShadeProgress.asStateFlow()
 
     private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null)
-    override val currentFling: StateFlow<FlingInfo?> = _currentFling
+    override val currentFling: StateFlow<FlingInfo?> = _currentFling.asStateFlow()
 
     private val _lockscreenShadeExpansion = MutableStateFlow(0f)
-    override val lockscreenShadeExpansion = _lockscreenShadeExpansion
+    override val lockscreenShadeExpansion = _lockscreenShadeExpansion.asStateFlow()
 
     private val _legacyShadeExpansion = MutableStateFlow(0f)
+
     @Deprecated("Use ShadeInteractor instead")
-    override val legacyShadeExpansion = _legacyShadeExpansion
+    override val legacyShadeExpansion = _legacyShadeExpansion.asStateFlow()
 
     private val _legacyShadeTracking = MutableStateFlow(false)
+
     @Deprecated("Use ShadeInteractor instead")
-    override val legacyShadeTracking = _legacyShadeTracking
+    override val legacyShadeTracking = _legacyShadeTracking.asStateFlow()
 
     private val _legacyQsTracking = MutableStateFlow(false)
-    @Deprecated("Use ShadeInteractor instead") override val legacyQsTracking = _legacyQsTracking
+
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyQsTracking = _legacyQsTracking.asStateFlow()
 
     private val _legacyExpandedOrAwaitingInputTransfer = MutableStateFlow(false)
+
     @Deprecated("Use ShadeInteractor instead")
-    override val legacyExpandedOrAwaitingInputTransfer = _legacyExpandedOrAwaitingInputTransfer
+    override val legacyExpandedOrAwaitingInputTransfer =
+        _legacyExpandedOrAwaitingInputTransfer.asStateFlow()
 
     private val _legacyIsQsExpanded = MutableStateFlow(false)
-    @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyIsQsExpanded = _legacyIsQsExpanded.asStateFlow()
 
     @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
     override val legacyLockscreenShadeTracking = MutableStateFlow(false)
 
-    private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
-    override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
-
     private var _isDualShadeAlignedToBottom = false
     override val isDualShadeAlignedToBottom
         get() = _isDualShadeAlignedToBottom
 
+    private var _isShadeLayoutWide = MutableStateFlow(false)
+    override val isShadeLayoutWide: StateFlow<Boolean> = _isShadeLayoutWide.asStateFlow()
+
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
         _legacyIsQsExpanded.value = legacyIsQsExpanded
     }
 
     private val _legacyExpandImmediate = MutableStateFlow(false)
+
     @Deprecated("Use ShadeInteractor instead")
-    override val legacyExpandImmediate = _legacyExpandImmediate
+    override val legacyExpandImmediate = _legacyExpandImmediate.asStateFlow()
 
     @Deprecated("Use ShadeInteractor instead")
     override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) {
@@ -106,6 +117,7 @@
     }
 
     private val _legacyQsFullscreen = MutableStateFlow(false)
+
     @Deprecated("Use ShadeInteractor instead") override val legacyQsFullscreen = _legacyQsFullscreen
 
     @Deprecated("Use ShadeInteractor instead")
@@ -114,6 +126,7 @@
     }
 
     private val _legacyIsClosing = MutableStateFlow(false)
+
     @Deprecated("Use ShadeInteractor instead") override val legacyIsClosing = _legacyIsClosing
 
     @Deprecated("Use ShadeInteractor instead")
@@ -142,13 +155,13 @@
         _legacyShadeExpansion.value = expandedFraction
     }
 
-    override fun setShadeMode(mode: ShadeMode) {
-        _shadeMode.value = mode
-    }
-
     fun setDualShadeAlignedToBottom(isAlignedToBottom: Boolean) {
         _isDualShadeAlignedToBottom = isAlignedToBottom
     }
+
+    override fun setShadeLayoutWide(isShadeLayoutWide: Boolean) {
+        _isShadeLayoutWide.value = isShadeLayoutWide
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index a00d2f4..bfd6614 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -45,7 +45,6 @@
             scope = applicationCoroutineScope,
             sceneInteractor = sceneInteractor,
             sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
-            shadeRepository = shadeRepository,
         )
     }
 val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
deleted file mode 100644
index 862e52d..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.systemui.smartspace.data.repository
-
-import android.app.smartspace.SmartspaceTarget
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSmartspaceRepository(
-    smartspaceRemoteViewsEnabled: Boolean = true,
-) : SmartspaceRepository {
-
-    override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled
-
-    private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
-        MutableStateFlow(emptyList())
-    override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
-        _communalSmartspaceTargets
-
-    fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) {
-        _communalSmartspaceTargets.value = targets
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
deleted file mode 100644
index 0e4c923..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2024 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.smartspace.data.repository
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-
-val Kosmos.fakeSmartspaceRepository by Fixture { FakeSmartspaceRepository() }
-
-val Kosmos.smartspaceRepository by Fixture<SmartspaceRepository> { fakeSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 144fe26..2335f21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
 import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
@@ -34,6 +33,5 @@
             mediaRouterChipInteractor = mediaRouterChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
-            dialogTransitionAnimator = mockDialogTransitionAnimator,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 1d06947..2773f82 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
 import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
@@ -31,7 +30,6 @@
             context = applicationContext,
             interactor = screenRecordChipInteractor,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
-            dialogTransitionAnimator = mockDialogTransitionAnimator,
             systemClock = fakeSystemClock,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 2e475a3..1b3108c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
 import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
@@ -32,6 +31,5 @@
             mediaProjectionChipInteractor = mediaProjectionChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
-            dialogTransitionAnimator = mockDialogTransitionAnimator,
         )
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9fc64a9..099cb28 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,7 +26,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Region;
-import android.hardware.input.InputManager;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -56,7 +55,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -748,8 +746,6 @@
 
         if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
             mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
-                    Objects.requireNonNull(mContext.getSystemService(
-                            InputManager.class)),
                     Looper.myLooper(),
                     Display.DEFAULT_DISPLAY);
             addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index b061065..3706dcc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -234,6 +234,8 @@
         mAccessibilityShortcutKeyTargets.clear();
         mAccessibilityButtonTargets.clear();
         mAccessibilityGestureTargets.clear();
+        mAccessibilityQsTargets.clear();
+        mA11yTilesInQsPanel.clear();
         mTargetAssignedToAccessibilityButton = null;
         mIsTouchExplorationEnabled = false;
         mServiceHandlesDoubleTap = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 3f0f23f..56da231 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -23,7 +23,6 @@
 import android.annotation.RequiresPermission;
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
-import android.hardware.input.InputManager;
 import android.hardware.input.VirtualMouse;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseConfig;
@@ -60,8 +59,8 @@
  * In case multiple physical keyboard are connected to a device,
  * mouse keys of each physical keyboard will control a single (global) mouse pointer.
  */
-public class MouseKeysInterceptor extends BaseEventStreamTransformation implements Handler.Callback,
-        InputManager.InputDeviceListener {
+public class MouseKeysInterceptor extends BaseEventStreamTransformation
+        implements Handler.Callback {
     private static final String LOG_TAG = "MouseKeysInterceptor";
 
     // To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
@@ -77,11 +76,8 @@
     private static final int INTERVAL_MILLIS = 10;
 
     private final AccessibilityManagerService mAms;
-    private final InputManager mInputManager;
     private final Handler mHandler;
 
-    private final int mDisplayId;
-
     VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
 
     private VirtualMouse mVirtualMouse = null;
@@ -100,23 +96,23 @@
     /** Last time the key action was performed */
     private long mLastTimeKeyActionPerformed = 0;
 
-    // TODO (b/346706749): This is currently using the numpad key bindings for mouse keys.
-    //  Decide the final mouse key bindings with UX input.
+    /** Whether scroll toggle is on */
+    private boolean mScrollToggleOn = false;
+
     public enum MouseKeyEvent {
-        DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_1),
-        DOWN_MOVE(KeyEvent.KEYCODE_NUMPAD_2),
-        DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_3),
-        LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_4),
-        RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_6),
-        DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_7),
-        UP_MOVE(KeyEvent.KEYCODE_NUMPAD_8),
-        DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_9),
-        LEFT_CLICK(KeyEvent.KEYCODE_NUMPAD_5),
-        RIGHT_CLICK(KeyEvent.KEYCODE_NUMPAD_DOT),
-        HOLD(KeyEvent.KEYCODE_NUMPAD_MULTIPLY),
-        RELEASE(KeyEvent.KEYCODE_NUMPAD_SUBTRACT),
-        SCROLL_UP(KeyEvent.KEYCODE_A),
-        SCROLL_DOWN(KeyEvent.KEYCODE_S);
+        DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
+        UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
+        DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9),
+        LEFT_MOVE(KeyEvent.KEYCODE_U),
+        RIGHT_MOVE(KeyEvent.KEYCODE_O),
+        DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J),
+        DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K),
+        DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L),
+        LEFT_CLICK(KeyEvent.KEYCODE_I),
+        RIGHT_CLICK(KeyEvent.KEYCODE_SLASH),
+        HOLD(KeyEvent.KEYCODE_M),
+        RELEASE(KeyEvent.KEYCODE_COMMA),
+        SCROLL_TOGGLE(KeyEvent.KEYCODE_PERIOD);
 
         private final int mKeyCode;
         MouseKeyEvent(int enumValue) {
@@ -149,22 +145,19 @@
      * Construct a new MouseKeysInterceptor.
      *
      * @param service The service to notify of key events
-     * @param inputManager InputManager to track changes to connected input devices
      * @param looper Looper to use for callbacks and messages
      * @param displayId Display ID to send mouse events to
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    public MouseKeysInterceptor(AccessibilityManagerService service, InputManager inputManager,
-            Looper looper, int displayId) {
+    public MouseKeysInterceptor(AccessibilityManagerService service, Looper looper, int displayId) {
         mAms = service;
-        mInputManager = inputManager;
         mHandler = new Handler(looper, this);
-        mInputManager.registerInputDeviceListener(this, mHandler);
-        mDisplayId = displayId;
         // Create the virtual mouse on a separate thread since virtual device creation
         // should happen on an auxiliary thread, and not from the handler's thread.
+        // This is because virtual device creation is a blocking operation and can cause a
+        // deadlock if it is called from the handler's thread.
         new Thread(() -> {
-            mVirtualMouse = createVirtualMouse();
+            mVirtualMouse = createVirtualMouse(displayId);
         }).start();
 
     }
@@ -193,22 +186,23 @@
 
     /**
      * Performs a mouse scroll action based on the provided key code.
+     * The scroll action will only be performed if the scroll toggle is on.
      * This method interprets the key code as a mouse scroll and sends
      * the corresponding {@code VirtualMouseScrollEvent#mYAxisMovement}.
 
      * @param keyCode The key code representing the mouse scroll action.
      *                Supported keys are:
      *                <ul>
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_UP}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_DOWN}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
      *                </ul>
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void performMouseScrollAction(int keyCode) {
         MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
         float y = switch (mouseKeyEvent) {
-            case SCROLL_UP -> 1.0f;
-            case SCROLL_DOWN -> -1.0f;
+            case UP_MOVE_OR_SCROLL -> 1.0f;
+            case DOWN_MOVE_OR_SCROLL -> -1.0f;
             default -> 0.0f;
         };
         if (mVirtualMouse != null) {
@@ -231,8 +225,8 @@
      * @param keyCode The key code representing the mouse button action.
      *                Supported keys are:
      *                <ul>
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT_CLICK} (Primary Button)
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT_CLICK} (Secondary
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_CLICK} (Primary Button)
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_CLICK} (Secondary
      *                  Button)
      *                </ul>
      */
@@ -264,17 +258,20 @@
      * The method calculates the relative movement of the mouse pointer
      * and sends the corresponding event to the virtual mouse.
      *
+     * The UP and DOWN pointer actions will only take place for their respective keys
+     * if the scroll toggle is off.
+     *
      * @param keyCode The key code representing the direction or button press.
      *                Supported keys are:
      *                <ul>
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_LEFT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DOWN}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_RIGHT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_LEFT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent UP}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_RIGHT}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE}
      *                </ul>
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -287,8 +284,10 @@
                 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
                 y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
             }
-            case DOWN_MOVE -> {
-                y = MOUSE_POINTER_MOVEMENT_STEP;
+            case DOWN_MOVE_OR_SCROLL -> {
+                if (!mScrollToggleOn) {
+                    y = MOUSE_POINTER_MOVEMENT_STEP;
+                }
             }
             case DIAGONAL_DOWN_RIGHT_MOVE -> {
                 x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -304,8 +303,10 @@
                 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
                 y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
             }
-            case UP_MOVE -> {
-                y = -MOUSE_POINTER_MOVEMENT_STEP;
+            case UP_MOVE_OR_SCROLL -> {
+                if (!mScrollToggleOn) {
+                    y = -MOUSE_POINTER_MOVEMENT_STEP;
+                }
             }
             case DIAGONAL_UP_RIGHT_MOVE -> {
                 x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -333,8 +334,8 @@
     }
 
     private boolean isMouseScrollKey(int keyCode) {
-        return keyCode == MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
-                || keyCode == MouseKeyEvent.SCROLL_DOWN.getKeyCodeValue();
+        return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCodeValue()
+                || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCodeValue();
     }
 
     /**
@@ -343,7 +344,7 @@
      * @return The created VirtualMouse.
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    private VirtualMouse createVirtualMouse() {
+    private VirtualMouse createVirtualMouse(int displayId) {
         final VirtualDeviceManagerInternal localVdm =
                 LocalServices.getService(VirtualDeviceManagerInternal.class);
         mVirtualDevice = localVdm.createVirtualDevice(
@@ -351,7 +352,7 @@
         VirtualMouse virtualMouse = mVirtualDevice.createVirtualMouse(
                 new VirtualMouseConfig.Builder()
                 .setInputDeviceName("Mouse Keys Virtual Mouse")
-                .setAssociatedDisplayId(mDisplayId)
+                .setAssociatedDisplayId(displayId)
                 .build());
         return virtualMouse;
     }
@@ -375,42 +376,56 @@
         if (!isMouseKey(keyCode)) {
             // Pass non-mouse key events to the next handler
             super.onKeyEvent(event, policyFlags);
-        } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
-            sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
-                    VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
-        } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
-            sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
-                    VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
-        } else if (isDown && isMouseButtonKey(keyCode)) {
-            performMouseButtonAction(keyCode);
-        } else if (isDown && isMouseScrollKey(keyCode)) {
-            // If the scroll key is pressed down and no other key is active,
-            // set it as the active key and send a message to scroll the pointer
-            if (mActiveScrollKey == KEY_NOT_SET) {
-                mActiveScrollKey = keyCode;
-                mLastTimeKeyActionPerformed = event.getDownTime();
-                mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
-            }
         } else if (isDown) {
-            // This is a directional key.
-            // If the key is pressed down and no other key is active,
-            // set it as the active key and send a message to move the pointer
-            if (mActiveMoveKey == KEY_NOT_SET) {
-                mActiveMoveKey = keyCode;
-                mLastTimeKeyActionPerformed = event.getDownTime();
-                mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+            if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCodeValue()) {
+                mScrollToggleOn = !mScrollToggleOn;
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, "Scroll toggle " + (mScrollToggleOn ? "ON" : "OFF"));
+                }
+            } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+                sendVirtualMouseButtonEvent(
+                        VirtualMouseButtonEvent.BUTTON_PRIMARY,
+                        VirtualMouseButtonEvent.ACTION_BUTTON_PRESS
+                );
+            } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+                sendVirtualMouseButtonEvent(
+                        VirtualMouseButtonEvent.BUTTON_PRIMARY,
+                        VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE
+                );
+            } else if (isMouseButtonKey(keyCode)) {
+                performMouseButtonAction(keyCode);
+            } else if (mScrollToggleOn && isMouseScrollKey(keyCode)) {
+                // If the scroll key is pressed down and no other key is active,
+                // set it as the active key and send a message to scroll the pointer
+                if (mActiveScrollKey == KEY_NOT_SET) {
+                    mActiveScrollKey = keyCode;
+                    mLastTimeKeyActionPerformed = event.getDownTime();
+                    mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
+                }
+            } else {
+                // This is a directional key.
+                // If the key is pressed down and no other key is active,
+                // set it as the active key and send a message to move the pointer
+                if (mActiveMoveKey == KEY_NOT_SET) {
+                    mActiveMoveKey = keyCode;
+                    mLastTimeKeyActionPerformed = event.getDownTime();
+                    mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+                }
             }
-        } else if (mActiveMoveKey == keyCode) {
-            // If the key is released, and it is the active key, stop moving the pointer
-            mActiveMoveKey = KEY_NOT_SET;
-            mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
-        } else if (mActiveScrollKey == keyCode) {
-            // If the key is released, and it is the active key, stop scrolling the pointer
-            mActiveScrollKey = KEY_NOT_SET;
-            mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
         } else {
-            Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
-                    + "', with no matching down event from deviceId = " + event.getDeviceId());
+            // Up event received
+            if (mActiveMoveKey == keyCode) {
+                // If the key is released, and it is the active key, stop moving the pointer
+                mActiveMoveKey = KEY_NOT_SET;
+                mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
+            } else if (mActiveScrollKey == keyCode) {
+                // If the key is released, and it is the active key, stop scrolling the pointer
+                mActiveScrollKey = KEY_NOT_SET;
+                mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
+            } else {
+                Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
+                        + "', with no matching down event from deviceId = " + event.getDeviceId());
+            }
         }
     }
 
@@ -470,14 +485,6 @@
         }
     }
 
-    @Override
-    public void onInputDeviceAdded(int deviceId) {
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-    }
-
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override
     public void onDestroy() {
@@ -485,14 +492,8 @@
         mActiveMoveKey = KEY_NOT_SET;
         mActiveScrollKey = KEY_NOT_SET;
         mLastTimeKeyActionPerformed = 0;
+
         mHandler.removeCallbacksAndMessages(null);
-
         mVirtualDevice.close();
-        mInputManager.unregisterInputDeviceListener(this);
     }
-
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
-    }
-
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 2e9a4dc..a10039f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -462,7 +462,9 @@
 
                         @Override
                         public void onShown() {
-                            mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+                            if (mCallback != null) {
+                                mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+                            }
                         }
 
                         @Override
@@ -511,7 +513,9 @@
 
                         @Override
                         public void startIntentSender(IntentSender intentSender) {
-                            mCallback.startIntentSenderAndFinishSession(intentSender);
+                            if (mCallback != null) {
+                                mCallback.startIntentSenderAndFinishSession(intentSender);
+                            }
                         }
 
                         private void log(int type) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 21947ba..95dbaae 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -1016,6 +1016,7 @@
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the
             // kernel log
             doSysRq('w');
+            doSysRq('m');
             doSysRq('l');
         }
 
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index ac9ed0d..3201223 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1233,6 +1233,10 @@
                             obsoleteAuthType.add(type);
                             // And delete it from the TABLE_META
                             accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
+                        } else if (knownUid != null && knownUid != uid) {
+                            Slog.w(TAG, "authenticator no longer exist for type " + type);
+                            obsoleteAuthType.add(type);
+                            accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 25fb729..cc476a3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -72,6 +72,7 @@
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
+import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
@@ -2322,7 +2323,9 @@
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mService.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-                mService.mPackageWatchdog.onPackagesReady();
+                if (!refactorCrashrecovery()) {
+                    mService.mPackageWatchdog.onPackagesReady();
+                }
                 mService.scheduleHomeTimeout();
             }
         }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 932b9c0..7f43fae 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -210,6 +210,7 @@
         "safety_center",
         "sensors",
         "spoon",
+        "stability",
         "statsd",
         "system_performance",
         "system_sw_touch",
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 0de3428..57bffa7 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -470,8 +470,13 @@
         if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) {
             return false;
         }
-        return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        try {
+            return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                    == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
+        }
+        return false;
     }
 
     /*package*/ synchronized boolean isBluetoothScoRequestedInternally() {
@@ -1150,12 +1155,16 @@
     }
 
     private void checkScoAudioState() {
-        if (mBluetoothHeadset != null
-                && mBluetoothHeadsetDevice != null
-                && mScoAudioState == SCO_STATE_INACTIVE
-                && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+        try {
+            if (mBluetoothHeadset != null
+                    && mBluetoothHeadsetDevice != null
+                    && mScoAudioState == SCO_STATE_INACTIVE
+                    && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                    != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
         }
     }
 
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
new file mode 100644
index 0000000..317c91e
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.crashrecovery;
+
+import android.content.Context;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.RescueParty;
+import com.android.server.SystemService;
+
+
+/** This class encapsulate the lifecycle methods of CrashRecovery module. */
+public class CrashRecoveryModule {
+    private static final String TAG = "CrashRecoveryModule";
+
+    /** Lifecycle definition for CrashRecovery module. */
+    public static class Lifecycle extends SystemService {
+        private Context mSystemContext;
+        private PackageWatchdog mPackageWatchdog;
+
+        public Lifecycle(Context context) {
+            super(context);
+            mSystemContext = context;
+            mPackageWatchdog = PackageWatchdog.getInstance(context);
+        }
+
+        @Override
+        public void onStart() {
+            RescueParty.registerHealthObserver(mSystemContext);
+            mPackageWatchdog.noteBoot();
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+                mPackageWatchdog.onPackagesReady();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
index 4a66bac..615db34 100644
--- a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -7,6 +7,9 @@
           "include-filter": "com.android.server.RescuePartyTest"
         }
       ]
+    },
+    {
+      "name": "CrashRecoveryModuleTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 88da6fb..550f68f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -660,7 +660,11 @@
                     .setPortId(physicalAddressToPortId(physicalAddress))
                     .setDeviceType(type)
                     .build();
-            updateCecDevice(updatedDeviceInfo);
+            if (deviceInfo.getPhysicalAddress() != physicalAddress) {
+                addCecDevice(updatedDeviceInfo);
+            } else {
+                updateCecDevice(updatedDeviceInfo);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 05f9364f..875380f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -335,6 +335,7 @@
     final Resources mRes;
     private final Handler mHandler;
 
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
     @NonNull
     private final Handler mIoHandler;
 
@@ -1017,7 +1018,6 @@
             final int userId = user.getUserIdentifier();
             SecureSettingsWrapper.onUserStarting(userId);
             synchronized (ImfLock.class) {
-                mService.getUserData(userId);
                 if (mService.mConcurrentMultiUserModeEnabled) {
                     if (mService.mCurrentUserId != userId && mService.mSystemReady) {
                         mService.initializeVisibleBackgroundUserLocked(userId);
@@ -1116,9 +1116,6 @@
             mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
                     bindingControllerForTesting != null ? bindingControllerForTesting
                             : bindingControllerFactory);
-            for (int id : mUserManagerInternal.getUserIds()) {
-                getUserData(id);
-            }
 
             mMenuController = new InputMethodMenuController(this);
             mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
@@ -1135,6 +1132,7 @@
             mHwController = new HandwritingModeController(mContext, uiLooper,
                     new InkWindowInitializer(), discardDelegationTextRunnable);
             registerDeviceListenerAndCheckStylusSupport();
+            mInputMethodManagerInternal = new LocalServiceImpl();
         }
     }
 
@@ -5589,7 +5587,12 @@
     }
 
     private void publishLocalService() {
-        LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
+        LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
+    }
+
+    // TODO(b/352228316): Remove it once IMMIProxy is removed.
+    InputMethodManagerInternal getLocalService(){
+        return mInputMethodManagerInternal;
     }
 
     private final class LocalServiceImpl extends InputMethodManagerInternal {
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 59411ad..5cd980f 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -74,16 +74,6 @@
                             }
                         });
                     }
-
-                    @Override
-                    public void onUserCreated(UserInfo user, Object unusedToken) {
-                        final int userId = user.id;
-                        handler.post(() -> {
-                            synchronized (ImfLock.class) {
-                                getOrCreate(userId);
-                            }
-                        });
-                    }
                 });
     }
 
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
index e50324e..f2714db 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -29,19 +29,22 @@
 public class ContextHubTestModeManager {
     private static final String TAG = "ContextHubTestModeManager";
 
-    /** Probability (in percent) of duplicating a message. */
-    private static final int MESSAGE_DROP_PROBABILITY_PERCENT = 20;
+    /** Probability of duplicating a message. */
+    private static final double MESSAGE_DROP_PROBABILITY = 0.05;
 
-    /** Probability (in percent) of duplicating a message. */
-    private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 20;
+    /** Probability of duplicating a message. */
+    private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05;
 
-    /** The number of total messages to send when the duplicate event happens. */
+    /** The number of total messages to send when the duplication event happens. */
     private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
 
-    /** A probability percent for a certain event. */
-    private static final int MAX_PROBABILITY_PERCENT = 100;
+    /**
+     * The seed for the random number generator. This is used to make the
+     * test more deterministic.
+     */
+    private static final long SEED = 0xDEADBEEF;
 
-    private final Random mRandom = new Random();
+    private final Random mRandom = new Random(SEED);
 
     /**
      * @return whether the message was handled
@@ -50,8 +53,7 @@
     public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
         if (Flags.reliableMessageDuplicateDetectionService()
                 && message.isReliable()
-                && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
-                        < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) {
+                && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) {
             Log.i(TAG, "[TEST MODE] Duplicating message ("
                     + NUM_MESSAGES_TO_DUPLICATE
                     + " sends) with message sequence number: "
@@ -71,8 +73,7 @@
     public boolean sendMessageToContextHub(NanoAppMessage message) {
         if (Flags.reliableMessageRetrySupportService()
                 && message.isReliable()
-                && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
-                        < MESSAGE_DROP_PROBABILITY_PERCENT) {
+                && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) {
             Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
                     + message.getMessageSequenceNumber());
             return true;
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 e3d5c54..803b125 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.media.projection;
 
 import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -34,10 +35,12 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AppOpsManager;
 import android.app.IProcessObserver;
+import android.app.KeyguardManager;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -78,6 +81,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.wm.WindowManagerInternal;
@@ -132,6 +136,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final PackageManager mPackageManager;
     private final WindowManagerInternal mWmInternal;
+    private final KeyguardManager mKeyguardManager;
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
@@ -147,7 +152,9 @@
         this(context, new Injector());
     }
 
-    @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) {
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    @VisibleForTesting
+    MediaProjectionManagerService(Context context, Injector injector) {
         super(context);
         mContext = context;
         mInjector = injector;
@@ -163,9 +170,47 @@
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
         mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
+        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mKeyguardManager.addKeyguardLockedStateListener(
+                mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
         Watchdog.getInstance().addMonitor(this);
     }
 
+    /**
+     * In order to record the keyguard, the MediaProjection package must be either:
+     *   - a holder of RECORD_SENSITIVE_CONTENT permission, or
+     *   - be one of the bugreport whitelisted packages
+     */
+    private boolean canCaptureKeyguard() {
+        if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+            return true;
+        }
+        synchronized (mLock) {
+            if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
+                return false;
+            }
+            if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
+                    mProjectionGrant.packageName)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            return SystemConfig.getInstance().getBugreportWhitelistedPackages()
+                    .contains(mProjectionGrant.packageName);
+        }
+    }
+
+    @VisibleForTesting
+    void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+        if (!isKeyguardLocked) return;
+        synchronized (mLock) {
+            if (mProjectionGrant != null && !canCaptureKeyguard()) {
+                Slog.d(TAG, "Content Recording: Stopped MediaProjection"
+                        + " due to keyguard lock");
+                mProjectionGrant.stop();
+            }
+        }
+    }
+
     /** Functional interface for providing time. */
     @VisibleForTesting
     interface Clock {
@@ -1252,6 +1297,11 @@
         @Override
         public void notifyVirtualDisplayCreated(int displayId) {
             notifyVirtualDisplayCreated_enforcePermission();
+            if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) {
+                Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection");
+                stop();
+                return;
+            }
             synchronized (mLock) {
                 mVirtualDisplayId = displayId;
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 009e9b8..8ee02dc4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -801,8 +801,9 @@
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-            target.sendIntent(context, 0, fillIn, null /* onFinished*/, null /* handler */,
-                    null /* requiredPermission */, options.toBundle());
+            target.sendIntent(context, 0, fillIn,
+                    null /* requiredPermission */, options.toBundle(),
+                    null /* executor */, null /* onFinished*/);
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 0d1095f..6b7b702 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -1194,8 +1194,9 @@
                 MODE_BACKGROUND_ACTIVITY_START_DENIED);
         for (IntentSender intentSender : unarchiveIntentSenders) {
             try {
-                intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
-                        /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+                intentSender.sendIntent(mContext, 0, broadcastIntent,
+                        /* requiredPermission */ null, options.toBundle(),
+                        /* executor */ null, /* onFinished */ null);
             } catch (IntentSender.SendIntentException e) {
                 Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
             } finally {
@@ -1328,8 +1329,9 @@
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityStartMode(
                     MODE_BACKGROUND_ACTIVITY_START_DENIED);
-            statusReceiver.sendIntent(mContext, 0, intent, /* onFinished= */ null,
-                    /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+            statusReceiver.sendIntent(mContext, 0, intent,
+                    /* requiredPermission */ null, options.toBundle(),
+                    /* executor */ null, /* onFinished */ null);
         } catch (IntentSender.SendIntentException e) {
             Slog.e(
                     TAG,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b93dcdc..f615ca1d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1581,8 +1581,9 @@
             try {
                 final BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-                callback.sendIntent(mContext, 0, intent, null /* onFinished*/,
-                        null /* handler */, null /* requiredPermission */, options.toBundle());
+                callback.sendIntent(mContext, 0, intent,
+                        null /* requiredPermission */, options.toBundle(),
+                        null /* executor */, null /* onFinished*/);
             } catch (SendIntentException ignore) {
             }
         });
@@ -1912,8 +1913,9 @@
             try {
                 final BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-                mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
-                        null /* handler */, null /* requiredPermission */, options.toBundle());
+                mTarget.sendIntent(mContext, 0, fillIn,
+                        null /* requiredPermission */, options.toBundle(),
+                        null /* executor */, null /* onFinished*/);
             } catch (SendIntentException ignored) {
             }
         }
@@ -1945,8 +1947,9 @@
             try {
                 final BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-                mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
-                        null /* handler */, null /* requiredPermission */, options.toBundle());
+                mTarget.sendIntent(mContext, 0, fillIn,
+                        null /* requiredPermission */, options.toBundle(),
+                        null /* executor */, null /* onFinished*/);
             } catch (SendIntentException ignored) {
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 47a79a3..ff8a69d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -5053,8 +5053,9 @@
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-            target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
-                    null /* handler */, null /* requiredPermission */, options.toBundle());
+            target.sendIntent(mContext, 0 /* code */, intent,
+                    null /* requiredPermission */, options.toBundle(),
+                    null /* executor */, null /* onFinished*/);
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -5447,8 +5448,9 @@
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-            target.sendIntent(context, 0, fillIn, null /* onFinished */,
-                    null /* handler */, null /* requiredPermission */, options.toBundle());
+            target.sendIntent(context, 0, fillIn,
+                    null /* requiredPermission */, options.toBundle(),
+                    null /* executor */, null /* onFinished*/);
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -5496,8 +5498,9 @@
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-            target.sendIntent(context, 0, fillIn, null /* onFinished */,
-                    null /* handler */, null /* requiredPermission */, options.toBundle());
+            target.sendIntent(context, 0, fillIn,
+                    null /* requiredPermission */, options.toBundle(),
+                    null /* executor */, null /* onFinished*/);
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -5533,8 +5536,9 @@
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setPendingIntentBackgroundActivityLaunchAllowed(false);
-            target.sendIntent(context, 0, intent, null /* onFinished */,
-                    null /* handler */, null /* requiredPermission */, options.toBundle());
+            target.sendIntent(context, 0, intent,
+                    null /* requiredPermission */, options.toBundle(),
+                    null /* executor */, null /* onFinished*/);
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2e63cdb..b5c33cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5034,8 +5034,8 @@
                         final BroadcastOptions options = BroadcastOptions.makeBasic();
                         options.setPendingIntentBackgroundActivityLaunchAllowed(false);
                         pi.sendIntent(null, success ? 1 : 0, null /* intent */,
-                                null /* onFinished*/, null /* handler */,
-                                null /* requiredPermission */, options.toBundle());
+                                null /* requiredPermission */, options.toBundle(),
+                                null /* executor */, null /* onFinished*/);
                     } catch (SendIntentException e) {
                         Slog.w(TAG, e);
                     }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4a60e45..021f7aa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -4490,8 +4490,9 @@
             ActivityOptions options = ActivityOptions.makeBasic()
                     .setPendingIntentBackgroundActivityStartMode(
                             MODE_BACKGROUND_ACTIVITY_START_DENIED);
-            intentSender.sendIntent(mContext, /* code= */ 0, extras,
-                    /* onFinished=*/ null, /* handler= */ null, null, options.toBundle());
+            intentSender.sendIntent(mContext, 0 /* code */, extras,
+                    null /* requiredPermission */, options.toBundle(),
+                    null /* executor */, null /* onFinished*/);
         } catch (SendIntentException e) {
             Slog.w(TAG, "sendIntent failed().", e);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b9a9d64f..dde9943 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1176,6 +1176,30 @@
         }
     }
 
+    /** Marks the user as slated for deletion during boot if necessary. **/
+    @GuardedBy("mUsersLock")
+    private void markUserForRemovalIfNecessaryLU(UserInfo ui) {
+        if (!ui.isEphemeral()) {
+            // User should be ephemeral to be marked for removal.
+            return;
+        }
+        if (ui.preCreated) {
+            // Avoid marking pre-created users for removal.
+            return;
+        }
+        if (ui.lastLoggedInTime == 0 && ui.isGuest() && Resources.getSystem().getBoolean(
+                com.android.internal.R.bool.config_guestUserAutoCreated)) {
+            // Avoid marking auto-created but not-yet-logged-in guest user for removal. Because a
+            // new one will be created anyway, and this one doesn't have any personal data in it yet
+            // due to not being logged in.
+            return;
+        }
+        // Mark the user for removal.
+        addRemovingUserIdLocked(ui.id);
+        ui.partial = true;
+        ui.flags |= UserInfo.FLAG_DISABLED;
+    }
+
     /* Prunes out any partially created or partially removed users. */
     private void cleanupPartialUsers() {
         ArrayList<UserInfo> partials = new ArrayList<>();
@@ -1901,6 +1925,7 @@
                 Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
                 return;
             }
+            UserManager.invalidateQuietModeEnabledCache();
             profile.flags ^= UserInfo.FLAG_QUIET_MODE;
             profileUserData = getUserDataLU(profile.id);
         }
@@ -4340,13 +4365,7 @@
                                             || mNextSerialNumber <= userData.info.id) {
                                         mNextSerialNumber = userData.info.id + 1;
                                     }
-                                    if (userData.info.isEphemeral() && !userData.info.preCreated
-                                            && userData.info.id != UserHandle.USER_SYSTEM) {
-                                        // Mark ephemeral user as slated for deletion.
-                                        addRemovingUserIdLocked(userData.info.id);
-                                        userData.info.partial = true;
-                                        userData.info.flags |= UserInfo.FLAG_DISABLED;
-                                    }
+                                    markUserForRemovalIfNecessaryLU(userData.info);
                                 }
                             }
                         } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index f85b8cc..aa5f5a24 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -206,7 +206,6 @@
         mPolicy = policy;
         mFaceDownDetector = faceDownDetector;
         mScreenUndimDetector = screenUndimDetector;
-        mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, null);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -214,6 +213,7 @@
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mTrustManager = mContext.getSystemService(TrustManager.class);
         mVibrator = mContext.getSystemService(Vibrator.class);
+        mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, null);
 
         mHandler = new NotifierHandler(looper);
         mBackgroundExecutor = backgroundExecutor;
@@ -813,6 +813,8 @@
         if (DEBUG) {
             Slog.d(TAG, "onScreenPolicyUpdate: newPolicy=" + newPolicy);
         }
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                SystemClock.uptimeMillis(), displayGroupId, newPolicy);
 
         synchronized (mLock) {
             Message msg = mHandler.obtainMessage(MSG_SCREEN_POLICY);
diff --git a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
index d57cd5d..3546565 100644
--- a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
+++ b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
@@ -16,8 +16,11 @@
 
 package com.android.server.power;
 
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
 import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
 import static android.os.PowerManagerInternal.isInteractive;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.power.PowerManagerService.DEFAULT_SCREEN_OFF_TIMEOUT;
 import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_NON_INTERACTIVE;
@@ -34,6 +37,9 @@
 import android.app.SynchronousUserSwitchObserver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
@@ -44,9 +50,13 @@
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -117,9 +127,42 @@
     @Retention(RetentionPolicy.SOURCE)
     private @interface OverrideOutcome {}
 
-    private static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER;
-    private static final long TIMEOUT_USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L;
+    private static final int POLICY_REASON_UNKNOWN = FrameworkStatsLog
+            .SCREEN_DIM_REPORTED__POLICY_REASON__UNKNOWN;
+    @VisibleForTesting
+    protected static final int POLICY_REASON_OFF_TIMEOUT = FrameworkStatsLog
+            .SCREEN_DIM_REPORTED__POLICY_REASON__OFF_TIMEOUT;
+    @VisibleForTesting
+    protected static final int POLICY_REASON_OFF_POWER_BUTTON = FrameworkStatsLog
+            .SCREEN_DIM_REPORTED__POLICY_REASON__OFF_POWER_BUTTON;
+    @VisibleForTesting
+    protected static final int POLICY_REASON_BRIGHT_UNDIM = FrameworkStatsLog
+            .SCREEN_DIM_REPORTED__POLICY_REASON__BRIGHT_UNDIM;
+    @VisibleForTesting
+    protected static final int POLICY_REASON_BRIGHT_INITIATED_REVERT = FrameworkStatsLog
+            .SCREEN_DIM_REPORTED__POLICY_REASON__BRIGHT_INITIATED_REVERT;
+
+    /**
+     * Policy Reason
+     * {@link android.os.statsd.power.ScreenDimReported.PolicyReason}.
+     */
+    @IntDef(prefix = {"POLICY_REASON_"}, value = {
+            POLICY_REASON_UNKNOWN,
+            POLICY_REASON_OFF_TIMEOUT,
+            POLICY_REASON_OFF_POWER_BUTTON,
+            POLICY_REASON_BRIGHT_UNDIM,
+            POLICY_REASON_BRIGHT_INITIATED_REVERT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface PolicyReason {}
+
+    @VisibleForTesting protected static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER;
+    private static final long USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L;
     private static final long SEND_OVERRIDE_TIMEOUT_LOG_THRESHOLD_MILLIS = 1000L;
+    @VisibleForTesting
+    protected static final long SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS = 500L;
+
+    @VisibleForTesting protected static final Object HANDLER_TOKEN = new Object();
 
     private Context mContext;
     private int mScreenOffTimeoutMs;
@@ -130,17 +173,24 @@
     protected WakefulnessSessionFrameworkStatsLogger mWakefulnessSessionFrameworkStatsLogger;
     private final Clock mClock;
     private final Object mLock = new Object();
+    private final Handler mHandler;
 
-    public WakefulnessSessionObserver(Context context, Injector injector) {
+    private DisplayManagerInternal mDisplayManagerInternal;
+    private int mPhysicalDisplayPortIdForDefaultDisplay;
+
+    public WakefulnessSessionObserver(
+            Context context, Injector injector) {
         if (injector == null) {
             injector = new Injector();
         }
 
         mContext = context;
+        mDisplayManagerInternal = injector.getDisplayManagerInternal();
         mWakefulnessSessionFrameworkStatsLogger = injector
                 .getWakefulnessSessionFrameworkStatsLogger();
         mClock = injector.getClock();
-        updateSettingScreenOffTimeout(context);
+        mHandler = injector.getHandler();
+        updateSettingScreenOffTimeout(mContext);
 
         try {
             final UserSwitchObserver observer = new UserSwitchObserver();
@@ -164,6 +214,31 @@
                         },
                         UserHandle.USER_ALL);
 
+        mPhysicalDisplayPortIdForDefaultDisplay = getPhysicalDisplayPortId(DEFAULT_DISPLAY);
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        if (displayManager != null) {
+            displayManager.registerDisplayListener(
+                    new DisplayManager.DisplayListener() {
+                        @Override
+                        public void onDisplayChanged(int displayId) {
+                            if (displayId == DEFAULT_DISPLAY) {
+                                mPhysicalDisplayPortIdForDefaultDisplay = getPhysicalDisplayPortId(
+                                        DEFAULT_DISPLAY);
+                            }
+                        }
+
+                        @Override
+                        public void onDisplayAdded(int i) {
+                        }
+
+                        @Override
+                        public void onDisplayRemoved(int i) {
+                        }
+                    },
+                    mHandler,
+                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+        }
+
         mPowerGroups.append(
                 Display.DEFAULT_DISPLAY_GROUP,
                 new WakefulnessSessionPowerGroup(Display.DEFAULT_DISPLAY_GROUP));
@@ -186,6 +261,20 @@
     }
 
     /**
+     * Track the screen policy
+     *
+     * @param eventTime policy changing time, in uptime millis.
+     * @param powerGroupId Power Group Id for this screen policy
+     * @param newPolicy Screen Policy defined in {@link DisplayPowerRequest}
+     */
+    public void onScreenPolicyUpdate(long eventTime, int powerGroupId, int newPolicy) {
+        if (!mPowerGroups.contains(powerGroupId)) {
+            mPowerGroups.append(powerGroupId, new WakefulnessSessionPowerGroup(powerGroupId));
+        }
+        mPowerGroups.get(powerGroupId).onScreenPolicyUpdate(eventTime, newPolicy);
+    }
+
+    /**
      * Track the system wakefulness
      *
      * @param powerGroupId Power Group Id for this wakefulness changes
@@ -267,6 +356,14 @@
         }
     }
 
+    private int getPhysicalDisplayPortId(int displayId) {
+        if (mDisplayManagerInternal == null) {
+            return -1;
+        }
+        DisplayInfo display = mDisplayManagerInternal.getDisplayInfo(displayId);
+        return ((DisplayAddress.Physical) display.address).getPort();
+    }
+
     private int getScreenOffTimeout() {
         synchronized (mLock) {
             return mScreenOffTimeoutMs;
@@ -277,10 +374,9 @@
     @VisibleForTesting
     protected class WakefulnessSessionPowerGroup {
         private static final long TIMEOUT_OFF_RESET_TIMESTAMP = -1;
-
         private int mPowerGroupId;
         private int mCurrentWakefulness;
-        private boolean mIsInteractive = false;
+        @VisibleForTesting protected boolean mIsInteractive = false;
         // state on start timestamp: will be used in state off to calculate the duration of state on
         private long mInteractiveStateOnStartTimestamp;
         @VisibleForTesting
@@ -295,6 +391,17 @@
         private int mTimeoutOverrideWakeLockCounter = 0;
         // The timestamp when Override Timeout is set to false
         private @ScreenTimeoutOverridePolicy.ReleaseReason int mTimeoutOverrideReleaseReason;
+        // The timestamp when current screen policy is set
+        private long mCurrentScreenPolicyTimestamp;
+        // current screen policy
+        private int mCurrentScreenPolicy;
+        // The screen policy before the current one
+        private int mPrevScreenPolicy;
+        // The previous screen policy duration
+        private int mPrevScreenPolicyDurationMs;
+        // The past dim duration
+        @VisibleForTesting protected int mPastDimDurationMs;
+        private long mInteractiveOffTimestamp;
         // The timestamp when state off by timeout occurs
         // will set TIMEOUT_OFF_RESET_TIMESTAMP if state on or state off by power button
         private long mTimeoutOffTimestamp;
@@ -307,6 +414,10 @@
             mPrevUserActivityEvent = DEFAULT_USER_ACTIVITY;
             mPrevUserActivityTimestamp = -1;
             mPowerGroupId = powerGroupId;
+            mCurrentScreenPolicy = mPrevScreenPolicy = POLICY_BRIGHT;
+            mCurrentScreenPolicyTimestamp = 0;
+            mPrevScreenPolicyDurationMs = 0;
+            mPastDimDurationMs = 0;
         }
 
         public void notifyUserActivity(long eventTime, @PowerManager.UserActivityEvent int event) {
@@ -320,6 +431,21 @@
             mCurrentUserActivityTimestamp = eventTime;
         }
 
+        public void onScreenPolicyUpdate(long eventTime, int newPolicy) {
+            if (newPolicy == mCurrentScreenPolicy) {
+                return;
+            }
+
+            if (newPolicy == POLICY_BRIGHT) {
+                checkAndLogDimIfQualified(POLICY_REASON_BRIGHT_UNDIM, eventTime);
+            }
+
+            mPrevScreenPolicy = mCurrentScreenPolicy;
+            mCurrentScreenPolicy = newPolicy;
+            mPrevScreenPolicyDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp);
+            mCurrentScreenPolicyTimestamp = eventTime;
+        }
+
         public void onWakefulnessChangeStarted(int wakefulness, int changeReason, long eventTime) {
             mCurrentWakefulness = wakefulness;
             if (mIsInteractive == isInteractive(wakefulness)) {
@@ -331,10 +457,10 @@
                 mInteractiveStateOnStartTimestamp = eventTime;
 
                 // Log the outcome of screen timeout override (USER INITIATED REVERT),
-                // when user initiates to revert the screen off state in a short period.
+                // when user initiates to revert the off state in a short period.
                 if (mTimeoutOffTimestamp != TIMEOUT_OFF_RESET_TIMESTAMP) {
-                    long offToOnDurationMs = eventTime - mTimeoutOffTimestamp;
-                    if (offToOnDurationMs < TIMEOUT_USER_INITIATED_REVERT_THRESHOLD_MILLIS) {
+                    long timeoutOffToOnDurationMs = eventTime - mTimeoutOffTimestamp;
+                    if (timeoutOffToOnDurationMs < USER_INITIATED_REVERT_THRESHOLD_MILLIS) {
                         mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent(
                                 mPowerGroupId,
                                 OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT,
@@ -344,11 +470,15 @@
                     }
                     mTimeoutOffTimestamp = TIMEOUT_OFF_RESET_TIMESTAMP;
                 }
+
+                checkAndLogDimIfQualified(POLICY_REASON_BRIGHT_INITIATED_REVERT, eventTime);
+
             } else {
                 int lastUserActivity = mCurrentUserActivityEvent;
                 long lastUserActivityDurationMs = eventTime - mCurrentUserActivityTimestamp;
                 @OffReason int interactiveStateOffReason = OFF_REASON_UNKNOWN;
                 int reducedInteractiveStateOnDurationMs = 0;
+                mInteractiveOffTimestamp = eventTime;
 
                 if (changeReason == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) {
                     interactiveStateOffReason = OFF_REASON_POWER_BUTTON;
@@ -369,6 +499,9 @@
                         mSendOverrideTimeoutLogTimestamp = eventTime;
                         mTimeoutOverrideReleaseReason = RELEASE_REASON_UNKNOWN; // reset the reason
                     }
+
+                    checkAndLogDimIfQualified(POLICY_REASON_OFF_POWER_BUTTON, eventTime);
+
                 } else if (changeReason == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
                     // Interactive Off reason is timeout
                     interactiveStateOffReason = OFF_REASON_TIMEOUT;
@@ -393,6 +526,8 @@
                         // state instantly
                         mTimeoutOffTimestamp = eventTime;
                     }
+
+                    checkAndLogDimIfQualified(POLICY_REASON_OFF_TIMEOUT, eventTime);
                 }
 
                 long interactiveStateOnDurationMs =
@@ -462,6 +597,106 @@
             }
         }
 
+        private void checkAndLogDimIfQualified(
+                @PolicyReason int reasonToBeChecked, long eventTime) {
+            // Only log dim event when DEFAULT_DISPLAY
+            if (mPowerGroupId != DEFAULT_DISPLAY) {
+                return;
+            }
+
+            int dimDurationMs = 0;
+            int lastUserActivity = mCurrentUserActivityEvent;
+            int lastUserActivityDurationMs = (int) (eventTime - mCurrentUserActivityTimestamp);
+            switch (reasonToBeChecked) {
+                case POLICY_REASON_OFF_TIMEOUT: {
+                    // The policy ordering:
+                    // (1) --DIM--OFF/DOZE->| or (2) --DIM->| because OFF/DOZE hasn't been updated.
+                    dimDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp); //(1)--DIM->|
+                    if (mPrevScreenPolicy == POLICY_DIM) {  // for (2) --DIM--OFF/DOZE->|
+                        dimDurationMs = mPrevScreenPolicyDurationMs;
+                    }
+                    mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+                            mPhysicalDisplayPortIdForDefaultDisplay,
+                            reasonToBeChecked,
+                            lastUserActivity,
+                            lastUserActivityDurationMs,
+                            dimDurationMs,
+                            mScreenOffTimeoutMs);
+                    mPastDimDurationMs = dimDurationMs;
+                    return;
+                }
+                case POLICY_REASON_OFF_POWER_BUTTON: {
+                    // Power Off will be triggered by USER_ACTIVITY_EVENT_BUTTON
+                    // The metric wants to record the previous activity before EVENT_BUTTON
+                    lastUserActivity = mPrevUserActivityEvent;
+                    lastUserActivityDurationMs = (int) (eventTime - mPrevUserActivityTimestamp);
+                    // the policy ordering:
+                    // (1) ---BRIGHT->| or (2) ---DIM->| because OFF/DOZE hasn't been updated
+                    dimDurationMs = 0; // for (1) ---BRIGHT->| which doesn't have dim (no need log)
+                    if (mCurrentScreenPolicy == POLICY_DIM) { // for (2) ---DIM->|
+                        dimDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp);
+                        mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+                                mPhysicalDisplayPortIdForDefaultDisplay,
+                                reasonToBeChecked,
+                                lastUserActivity,
+                                lastUserActivityDurationMs,
+                                dimDurationMs,
+                                mScreenOffTimeoutMs);
+                        mHandler.removeCallbacksAndMessages(HANDLER_TOKEN);
+                    }
+
+                    mPastDimDurationMs = dimDurationMs;
+                    return;
+                }
+                case POLICY_REASON_BRIGHT_UNDIM: {
+                    // Has checked the latest screen policy is POLICY_BRIGHT in onScreenPolicyUpdate
+                    if (mCurrentScreenPolicy == POLICY_DIM) { // policy ordering: --DIM--BRIGHT->|
+                        int savedDimDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp);
+                        int savedLastUserActivity = lastUserActivity;
+                        int savedLastUserActivityDurationMs = lastUserActivityDurationMs;
+
+                        // For the undim case --DIM--BRIGHT->|, it needs wait 500 ms to
+                        // differentiate between "power button off" case, which is
+                        // --DIM--BRIGHT(<500ms)--OFF/DOZE->|
+                        // [Method] Wait 500 ms to see whether triggers power button off or not.
+                        // [Reason] We got --DIM--BRIGHT->|. However, if BRIGHT is so short (<500ms)
+                        //          and follows OFF/DOZE, it represents power button off, not undim.
+                        //          It is normal to have a short BRIGHT for power button off because
+                        //          the system need to play an animation before off.
+                        mHandler.postDelayed(() -> {
+                            mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+                                    mPhysicalDisplayPortIdForDefaultDisplay,
+                                    reasonToBeChecked,
+                                    savedLastUserActivity,
+                                    savedLastUserActivityDurationMs,
+                                    savedDimDurationMs,
+                                    mScreenOffTimeoutMs);
+                            mPastDimDurationMs = savedDimDurationMs;
+                        }, HANDLER_TOKEN, SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS);
+                    }
+                    return;
+                }
+                case POLICY_REASON_BRIGHT_INITIATED_REVERT: {
+                    // the dimDuration in BRIGHT_INITIATE_REVERT is for the dim duration before
+                    // screen interactive off (mPastDimDurationMs)
+                    long offToOnDurationMs = eventTime - mInteractiveOffTimestamp;
+                    if (mPastDimDurationMs > 0
+                            && offToOnDurationMs < USER_INITIATED_REVERT_THRESHOLD_MILLIS) {
+                        mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+                                mPhysicalDisplayPortIdForDefaultDisplay,
+                                reasonToBeChecked,
+                                lastUserActivity,
+                                lastUserActivityDurationMs,
+                                mPastDimDurationMs,
+                                mScreenOffTimeoutMs);
+                    }
+                    return;
+                }
+                default:
+                    return;
+            }
+        }
+
         void dump(IndentingPrintWriter writer) {
             final long now = mClock.uptimeMillis();
 
@@ -475,6 +710,12 @@
             final long prevUserActivityDurationMs = now - mPrevUserActivityTimestamp;
             writer.println("previous user activity duration: " + prevUserActivityDurationMs);
             writer.println("is in override timeout: " + isInOverrideTimeout());
+            writer.println("mIsInteractive: " + mIsInteractive);
+            writer.println("current screen policy: " + mCurrentScreenPolicy);
+            final long currentScreenPolicyDurationMs = now - mCurrentScreenPolicyTimestamp;
+            writer.println("current screen policy duration: " + currentScreenPolicyDurationMs);
+            writer.println("previous screen policy: " + mPrevScreenPolicy);
+            writer.println("past screen policy duration: " + mPrevScreenPolicyDurationMs);
             writer.decreaseIndent();
         }
     }
@@ -512,6 +753,24 @@
                     (long) defaultTimeoutMs);
         }
 
+        public void logDimEvent(
+                int physicalDisplayPortId,
+                @PolicyReason int policyReason,
+                @PowerManager.UserActivityEvent int userActivityEvent,
+                int lastUserActivityEventDurationMs,
+                int dimDurationMs,
+                int defaultTimeoutMs) {
+            int logUserActivityEvent = convertToLogUserActivityEvent(userActivityEvent);
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.SCREEN_DIM_REPORTED,
+                    physicalDisplayPortId,
+                    policyReason,
+                    logUserActivityEvent,
+                    lastUserActivityEventDurationMs,
+                    dimDurationMs,
+                    defaultTimeoutMs);
+        }
+
         private static final int USER_ACTIVITY_OTHER = FrameworkStatsLog
                 .SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__OTHER;
 
@@ -591,5 +850,13 @@
         Clock getClock() {
             return SystemClock::uptimeMillis;
         }
+
+        Handler getHandler() {
+            return BackgroundThread.getHandler();
+        }
+
+        DisplayManagerInternal getDisplayManagerInternal() {
+            return LocalServices.getService(DisplayManagerInternal.class);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 0041d39..dfccd1a 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1542,6 +1542,11 @@
         final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
                 NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);
 
+        // Set startTime before boot so that NetworkStats includes at least one full bucket.
+        // Set endTime in the future so that NetworkStats includes everything in the active bucket.
+        final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
+        final long endTime = currentTimeInMillis + bucketDuration;
+
         // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
         //  history when query in every second in order to show realtime statistics. However,
         //  this is not a good long-term solution since NetworkStatsService will make frequent
@@ -1552,9 +1557,7 @@
         }
 
         final android.app.usage.NetworkStats queryNonTaggedStats =
-                getNetworkStatsManager().querySummary(
-                        template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
-                        currentTimeInMillis);
+                getNetworkStatsManager().querySummary(template, startTime, endTime);
 
         final NetworkStats nonTaggedStats =
                 NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats);
@@ -1562,9 +1565,7 @@
         if (!includeTags) return nonTaggedStats;
 
         final android.app.usage.NetworkStats queryTaggedStats =
-                getNetworkStatsManager().queryTaggedSummary(template,
-                        currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
-                        currentTimeInMillis);
+                getNetworkStatsManager().queryTaggedSummary(template, startTime, endTime);
         final NetworkStats taggedStats =
                 NetworkStatsUtils.fromPublicNetworkStats(queryTaggedStats);
         queryTaggedStats.close();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0a7d017..c8340a9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,7 +53,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isFloating;
 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -89,12 +88,6 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
 import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
 import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -114,7 +107,6 @@
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
-import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -240,11 +232,10 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
+import static com.android.server.wm.DesktopModeLaunchParamsModifier.canEnterDesktopMode;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
@@ -343,7 +334,6 @@
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
@@ -485,9 +475,6 @@
     // finished destroying itself.
     private static final int DESTROY_TIMEOUT = 10 * 1000;
 
-    // Rounding tolerance to be used in aspect ratio computations
-    private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
-
     final ActivityTaskManagerService mAtmService;
     final ActivityCallerState mCallerState;
     @NonNull
@@ -831,26 +818,6 @@
     // naturally.
     private boolean mInSizeCompatModeForBounds = false;
 
-    // Whether the aspect ratio restrictions applied to the activity bounds in applyAspectRatio().
-    private boolean mIsAspectRatioApplied = false;
-
-    // Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
-    // for fixed orientation. If not null, they are used as parent container in
-    // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets. If
-    // letterboxed due to fixed orientation then aspect ratio restrictions are also respected.
-    // This happens when an activity has fixed orientation which doesn't match orientation of the
-    // parent because a display is ignoring orientation request or fixed to user rotation.
-    // See WindowManagerService#getIgnoreOrientationRequest and
-    // WindowManagerService#getFixedToUserRotation for more context.
-    @Nullable
-    private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
-
-    // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
-    // aspect ratio. If not null, they are used as parent container in
-    // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
-    @Nullable
-    private Rect mLetterboxBoundsForAspectRatio;
-
     // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
     // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
     // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -8378,7 +8345,7 @@
      *         aspect ratio.
      */
     boolean shouldCreateCompatDisplayInsets() {
-        if (mLetterboxUiController.hasFullscreenOverride()) {
+        if (mAppCompatController.getAppCompatAspectRatioOverrides().hasFullscreenOverride()) {
             // If the user has forced the applications aspect ratio to be fullscreen, don't use size
             // compatibility mode in any situation. The user has been warned and therefore accepts
             // the risk of the application misbehaving.
@@ -8467,10 +8434,9 @@
                     fullConfig.windowConfiguration.getRotation());
         }
 
-        final Rect letterboxedContainerBounds =
-                mLetterboxBoundsForFixedOrientationAndAspectRatio != null
-                        ? mLetterboxBoundsForFixedOrientationAndAspectRatio
-                        : mLetterboxBoundsForAspectRatio;
+        final Rect letterboxedContainerBounds = mAppCompatController
+                .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
+
         // The role of CompatDisplayInsets is like the override bounds.
         mCompatDisplayInsets =
                 new CompatDisplayInsets(
@@ -8544,10 +8510,8 @@
             newParentConfiguration = mTmpConfig;
         }
 
-        mIsAspectRatioApplied = false;
+        mAppCompatController.getAppCompatAspectRatioPolicy().reset();
         mIsEligibleForFixedOrientationLetterbox = false;
-        mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
-        mLetterboxBoundsForAspectRatio = null;
         mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
                 isFixedRotationTransforming());
 
@@ -8580,8 +8544,11 @@
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio()
-                && !mLetterboxUiController.hasFullscreenOverride()) {
+        if (Flags.immersiveAppRepositioning()
+                && !mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForFixedOrientationAndAspectRatio()
+                && !mAppCompatController.getAppCompatAspectRatioOverrides()
+                    .hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
         final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
@@ -8601,8 +8568,12 @@
         // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
         // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (!Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio()
-                && !mInSizeCompatModeForBounds && !mLetterboxUiController.hasFullscreenOverride()) {
+        if (!Flags.immersiveAppRepositioning()
+                && !mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForFixedOrientationAndAspectRatio()
+                && !mInSizeCompatModeForBounds
+                && !mAppCompatController.getAppCompatAspectRatioOverrides()
+                    .hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
 
@@ -8619,14 +8590,16 @@
                 // Fixed orientation letterboxing is possible on both large screen devices
                 // with ignoreOrientationRequest enabled and on phones in split screen even with
                 // ignoreOrientationRequest disabled.
-                && (mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+                && (mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForFixedOrientationAndAspectRatio()
                         // Limiting check for aspect ratio letterboxing to devices with enabled
                         // ignoreOrientationRequest. This avoids affecting phones where apps may
                         // not expect the change of smallestScreenWidthDp after rotation which is
                         // possible with this logic. Not having smallestScreenWidthDp completely
                         // accurate on phones shouldn't make the big difference and is expected
                         // to be already well-tested by apps.
-                        || (isIgnoreOrientationRequest && mIsAspectRatioApplied))) {
+                        || (isIgnoreOrientationRequest
+                && mAppCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied()))) {
             // TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all
             // rotations and only re-calculate if parent bounds have non-orientation size change.
             resolvedConfig.smallestScreenWidthDp =
@@ -8667,7 +8640,14 @@
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
         }
 
-        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+        applySizeOverrideIfNeeded(
+                mDisplayContent,
+                info.applicationInfo,
+                newParentConfiguration,
+                resolvedConfig,
+                mOptOutEdgeToEdge,
+                hasFixedRotationTransform(),
+                getCompatDisplayInsets() != null);
         mResolveConfigHint.resetTmpOverrides();
 
         logAppCompatState();
@@ -8677,100 +8657,6 @@
         return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
     }
 
-    /**
-     * If necessary, override configuration fields related to app bounds.
-     * This will happen when the app is targeting SDK earlier than 35.
-     * The insets and configuration has decoupled since SDK level 35, to make the system
-     * compatible to existing apps, override the configuration with legacy metrics. In legacy
-     * metrics, fields such as appBounds will exclude some of the system bar areas.
-     * The override contains all potentially affected fields in Configuration, including
-     * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
-     * All overrides to those fields should be in this method.
-     *
-     * TODO: Consider integrate this with computeConfigByResolveHint()
-     */
-    private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
-            int parentWindowingMode, Configuration inOutConfig) {
-        if (mDisplayContent == null) {
-            return;
-        }
-        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
-        int rotation = newParentConfiguration.windowConfiguration.getRotation();
-        if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
-            rotation = mDisplayContent.getRotation();
-        }
-        if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
-                || getCompatDisplayInsets() != null
-                || (isFloating(parentWindowingMode)
-                        // Check the requested windowing mode of activity as well in case it is
-                        // switching between PiP and fullscreen.
-                        && (inOutConfig.windowConfiguration.getWindowingMode()
-                                == WINDOWING_MODE_UNDEFINED
-                                || isFloating(inOutConfig.windowConfiguration.getWindowingMode())))
-                || rotation == ROTATION_UNDEFINED)) {
-            // If the insets configuration decoupled logic is not enabled for the app, or the app
-            // already has a compat override, or the context doesn't contain enough info to
-            // calculate the override, skip the override.
-            return;
-        }
-        // Make sure the orientation related fields will be updated by the override insets, because
-        // fixed rotation has assigned the fields from display's configuration.
-        if (hasFixedRotationTransform()) {
-            inOutConfig.windowConfiguration.setAppBounds(null);
-            inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-            inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-            inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
-            inOutConfig.orientation = ORIENTATION_UNDEFINED;
-        }
-
-        // Override starts here.
-        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
-                : mDisplayContent.mBaseDisplayWidth;
-        final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
-                : mDisplayContent.mBaseDisplayHeight;
-        final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
-                .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
-        // This should be the only place override the configuration for ActivityRecord. Override
-        // the value if not calculated yet.
-        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-        if (outAppBounds == null || outAppBounds.isEmpty()) {
-            inOutConfig.windowConfiguration.setAppBounds(parentBounds);
-            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(nonDecorInsets);
-        }
-        float density = inOutConfig.densityDpi;
-        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
-            density = newParentConfiguration.densityDpi;
-        }
-        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-            inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
-        }
-        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-            inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
-        }
-        if (inOutConfig.smallestScreenWidthDp
-                == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
-                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // For the case of PIP transition and multi-window environment, the
-            // smallestScreenWidthDp is handled already. Override only if the app is in
-            // fullscreen.
-            final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
-            mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
-                    mDisplayContent.getDisplayMetrics().density,
-                    inOutConfig, true /* overrideConfig */);
-        }
-
-        // It's possible that screen size will be considered in different orientation with or
-        // without considering the system bar insets. Override orientation as well.
-        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
-            inOutConfig.orientation =
-                    (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
-                            ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
-        }
-    }
-
     private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
             @NonNull Configuration parentConfig) {
         task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
@@ -8830,11 +8716,13 @@
         // letterboxed for fixed orientation. Aspect ratio restrictions are also applied if
         // present. But this doesn't return true when the activity is letterboxed only because
         // of aspect ratio restrictions.
-        if (isLetterboxedForFixedOrientationAndAspectRatio()) {
+        if (mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
         }
         // Letterbox for limited aspect ratio.
-        if (isLetterboxedForAspectRatioOnly()) {
+        if (mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForAspectRatioOnly()) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
         }
 
@@ -8985,26 +8873,6 @@
     }
 
     /**
-     * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
-     * orientation then aspect ratio restrictions are also already respected.
-     *
-     * <p>This happens when an activity has fixed orientation which doesn't match orientation of the
-     * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link
-     * WindowManagerService#getIgnoreOrientationRequest} for more context.
-     */
-    boolean isLetterboxedForFixedOrientationAndAspectRatio() {
-        return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
-    }
-
-    boolean isLetterboxedForAspectRatioOnly() {
-        return mLetterboxBoundsForAspectRatio != null;
-    }
-
-    boolean isAspectRatioApplied() {
-        return mIsAspectRatioApplied;
-    }
-
-    /**
      * Whether this activity is eligible for letterbox eduction.
      *
      * <p>Conditions that need to be met:
@@ -9036,7 +8904,7 @@
      * @param parentBounds are the new parent bounds passed down to the activity and should be used
      *                     to compute the stable bounds.
      * @param outStableBounds will store the stable bounds, which are the bounds with insets
-     *                        applied, if orientation is not respected when insets are applied.
+     *                        applied, if orientation is not respected when insets are applied.g
      *                        Stable bounds should be used to compute letterboxed bounds if
      *                        orientation is not respected when insets are applied.
      * @param outNonDecorBounds will store the non decor bounds, which are the bounds with non
@@ -9189,23 +9057,9 @@
         final Rect prevResolvedBounds = new Rect(resolvedBounds);
         resolvedBounds.set(containingBounds);
 
-        final float letterboxAspectRatioOverride =
-                mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig);
-
-        // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
-        // be respected in #applyAspectRatio.
-        final float desiredAspectRatio;
-        if (isDefaultMultiWindowLetterboxAspectRatioDesired(newParentConfig)) {
-            desiredAspectRatio = DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-        } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
-            desiredAspectRatio = letterboxAspectRatioOverride;
-        } else {
-            desiredAspectRatio = computeAspectRatio(parentBounds);
-        }
-
-        // Apply aspect ratio to resolved bounds
-        mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
-                containingBounds, desiredAspectRatio);
+        mAppCompatController.getAppCompatAspectRatioPolicy()
+                .applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
+                        containingBoundsWithInsets, containingBounds);
 
         if (compatDisplayInsets != null) {
             compatDisplayInsets.getBoundsByRotation(mTmpBounds,
@@ -9233,21 +9087,8 @@
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
         mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
         computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
-        mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
-    }
-
-    /**
-     * Returns {@code true} if the default aspect ratio for a letterboxed app in multi-window mode
-     * should be used.
-     */
-    private boolean isDefaultMultiWindowLetterboxAspectRatioDesired(
-            @NonNull Configuration parentConfig) {
-        if (mDisplayContent == null) {
-            return false;
-        }
-        final int windowingMode = parentConfig.windowConfiguration.getWindowingMode();
-        return WindowConfiguration.inMultiWindowMode(windowingMode)
-                && !mDisplayContent.getIgnoreOrientationRequest();
+        mAppCompatController.getAppCompatAspectRatioPolicy()
+                .setLetterboxBoundsForFixedOrientationAndAspectRatio(new Rect(resolvedBounds));
     }
 
     /**
@@ -9264,7 +9105,8 @@
         // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
         // restricted size (resolved bounds may be the requested override bounds).
         mTmpBounds.setEmpty();
-        mIsAspectRatioApplied = applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
+        mAppCompatController.getAppCompatAspectRatioPolicy()
+                .applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
         // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
         // then they should be aligned later in #updateResolvedBoundsPosition()
         if (!mTmpBounds.isEmpty()) {
@@ -9275,7 +9117,8 @@
             // restrict, the bounds should be the requested override bounds.
             mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
             computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
-            mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
+            mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
         }
     }
 
@@ -9293,7 +9136,10 @@
         // activity will be displayed within them even if it is in size compat mode. They should be
         // saved here before resolved bounds are overridden below.
         final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
-                ? isAspectRatioApplied() : isLetterboxedForFixedOrientationAndAspectRatio();
+                ? mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isAspectRatioApplied()
+                : mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForFixedOrientationAndAspectRatio();
         final Rect containerBounds = useResolvedBounds
                 ? new Rect(resolvedBounds)
                 : newParentConfiguration.windowConfiguration.getBounds();
@@ -9337,8 +9183,9 @@
         resolvedBounds.set(containingBounds);
         // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
         if (!compatDisplayInsets.mIsFloating) {
-            mIsAspectRatioApplied =
-                    applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
+            mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
+                            containingBounds);
         }
 
         // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
@@ -9423,18 +9270,24 @@
     }
 
     void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
-        // Only allow to scale down.
         mSizeCompatScale = mAppCompatController.getTransparentPolicy()
                 .findOpaqueNotFinishingActivityBelow()
                 .map(activityRecord -> activityRecord.mSizeCompatScale)
-                .orElseGet(() -> {
-                    final int contentW = resolvedAppBounds.width();
-                    final int contentH = resolvedAppBounds.height();
-                    final int viewportW = containerAppBounds.width();
-                    final int viewportH = containerAppBounds.height();
-                    return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min(
-                            (float) viewportW / contentW, (float) viewportH / contentH);
-                });
+                .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+    }
+
+    private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
+        final int contentW = resolvedAppBounds.width();
+        final int contentH = resolvedAppBounds.height();
+        final int viewportW = containerAppBounds.width();
+        final int viewportH = containerAppBounds.height();
+        // Allow an application to be up-scaled if its window is smaller than its
+        // original container or if it's a freeform window in desktop mode.
+        boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
+                || (canEnterDesktopMode(mAtmService.mContext)
+                    && getWindowingMode() == WINDOWING_MODE_FREEFORM);
+        return shouldAllowUpscaling ? Math.min(
+                (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
     }
 
     private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
@@ -9742,190 +9595,15 @@
         return mPauseConfigurationDispatchCount > 0;
     }
 
-    private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
-            Rect containingBounds) {
-        return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
-                0 /* desiredAspectRatio */);
-    }
-
-    /**
-     * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
-     * made to outBounds.
-     *
-     * @return {@code true} if aspect ratio restrictions were applied.
-     */
-    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
-    private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
-            Rect containingBounds, float desiredAspectRatio) {
-        final float maxAspectRatio = getMaxAspectRatio();
-        final Task rootTask = getRootTask();
-        final float minAspectRatio = getMinAspectRatio();
-        final TaskFragment organizedTf = getOrganizedTaskFragment();
-        float aspectRatioToApply = desiredAspectRatio;
-        if (task == null || rootTask == null
-                || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1)
-                // Don't set aspect ratio if we are in VR mode.
-                || isInVrUiMode(getConfiguration())
-                // TODO(b/232898850): Always respect aspect ratio requests.
-                // Don't set aspect ratio for activity in ActivityEmbedding split.
-                || (organizedTf != null && !organizedTf.fillsParent())) {
-            return false;
-        }
-
-        final int containingAppWidth = containingAppBounds.width();
-        final int containingAppHeight = containingAppBounds.height();
-        final float containingRatio = computeAspectRatio(containingAppBounds);
-
-        if (aspectRatioToApply < 1) {
-            aspectRatioToApply = containingRatio;
-        }
-
-        if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) {
-            aspectRatioToApply = maxAspectRatio;
-        } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) {
-            aspectRatioToApply = minAspectRatio;
-        }
-
-        int activityWidth = containingAppWidth;
-        int activityHeight = containingAppHeight;
-
-        if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) {
-            if (containingAppWidth < containingAppHeight) {
-                // Width is the shorter side, so we use that to figure-out what the max. height
-                // should be given the aspect ratio.
-                activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f);
-            } else {
-                // Height is the shorter side, so we use that to figure-out what the max. width
-                // should be given the aspect ratio.
-                activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f);
-            }
-        } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
-            boolean adjustWidth;
-            switch (getRequestedConfigurationOrientation()) {
-                case ORIENTATION_LANDSCAPE:
-                    // Width should be the longer side for this landscape app, so we use the width
-                    // to figure-out what the max. height should be given the aspect ratio.
-                    adjustWidth = false;
-                    break;
-                case ORIENTATION_PORTRAIT:
-                    // Height should be the longer side for this portrait app, so we use the height
-                    // to figure-out what the max. width should be given the aspect ratio.
-                    adjustWidth = true;
-                    break;
-                default:
-                    // This app doesn't have a preferred orientation, so we keep the length of the
-                    // longer side, and use it to figure-out the length of the shorter side.
-                    if (containingAppWidth < containingAppHeight) {
-                        // Width is the shorter side, so we use the height to figure-out what the
-                        // max. width should be given the aspect ratio.
-                        adjustWidth = true;
-                    } else {
-                        // Height is the shorter side, so we use the width to figure-out what the
-                        // max. height should be given the aspect ratio.
-                        adjustWidth = false;
-                    }
-                    break;
-            }
-            if (adjustWidth) {
-                activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f);
-            } else {
-                activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f);
-            }
-        }
-
-        if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) {
-            // The display matches or is less than the activity aspect ratio, so nothing else to do.
-            return false;
-        }
-
-        // Compute configuration based on max or min supported width and height.
-        // Also account for the insets (e.g. display cutouts, navigation bar), which will be
-        // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out
-        // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise,
-        // the app bounds would end up too small. To achieve this we will also add clippable insets
-        // when the corresponding dimension fully fills the parent
-
-        int right = activityWidth + containingAppBounds.left;
-        int left = containingAppBounds.left;
-        if (right >= containingAppBounds.right) {
-            right = containingBounds.right;
-            left = containingBounds.left;
-        }
-        int bottom = activityHeight + containingAppBounds.top;
-        int top = containingAppBounds.top;
-        if (bottom >= containingAppBounds.bottom) {
-            bottom = containingBounds.bottom;
-            top = containingBounds.top;
-        }
-        outBounds.set(left, top, right, bottom);
-        return true;
-    }
-
     /**
      * Returns the min aspect ratio of this activity.
      */
     float getMinAspectRatio() {
-        if (mAppCompatController.getTransparentPolicy().isRunning()) {
-            return mAppCompatController.getTransparentPolicy().getInheritedMinAspectRatio();
-        }
-        if (info.applicationInfo == null) {
-            return info.getMinAspectRatio();
-        }
-        if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
-            return mLetterboxUiController.getUserMinAspectRatio();
-        }
-        if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
-                && !mAppCompatController.getAppCompatCameraOverrides()
-                    .shouldOverrideMinAspectRatioForCamera()) {
-            return info.getMinAspectRatio();
-        }
-        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
-                && !ActivityInfo.isFixedOrientationPortrait(
-                        getOverrideOrientation())) {
-            return info.getMinAspectRatio();
-        }
-
-        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
-                && isParentFullscreenPortrait()) {
-            // We are using the parent configuration here as this is the most recent one that gets
-            // passed to onConfigurationChanged when a relevant change takes place
-            return info.getMinAspectRatio();
-        }
-
-        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
-            return Math.max(mLetterboxUiController.getSplitScreenAspectRatio(),
-                    info.getMinAspectRatio());
-        }
-
-        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
-            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
-                    info.getMinAspectRatio());
-        }
-
-        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
-            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
-                    info.getMinAspectRatio());
-        }
-
-        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_SMALL)) {
-            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE,
-                    info.getMinAspectRatio());
-        }
-        return info.getMinAspectRatio();
-    }
-
-    private boolean isParentFullscreenPortrait() {
-        final WindowContainer parent = getParent();
-        return parent != null
-                && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
-                && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+        return mAppCompatController.getAppCompatAspectRatioPolicy().getMinAspectRatio();
     }
 
     float getMaxAspectRatio() {
-        if (mAppCompatController.getTransparentPolicy().isRunning()) {
-            return mAppCompatController.getTransparentPolicy().getInheritedMaxAspectRatio();
-        }
-        return info.getMaxAspectRatio();
+        return mAppCompatController.getAppCompatAspectRatioPolicy().getMaxAspectRatio();
     }
 
     /**
@@ -9936,18 +9614,6 @@
     }
 
     /**
-     * Returns the aspect ratio of the given {@code rect}.
-     */
-    static float computeAspectRatio(Rect rect) {
-        final int width = rect.width();
-        final int height = rect.height();
-        if (width == 0 || height == 0) {
-            return 0;
-        }
-        return Math.max(width, height) / (float) Math.min(width, height);
-    }
-
-    /**
      * @return {@code true} if this activity was reparented to another display but
      *         {@link #ensureActivityConfiguration} is not called.
      */
@@ -10201,8 +9867,8 @@
      */
     private boolean onlyVrUiModeChanged(int changes, Configuration lastReportedConfig) {
         final Configuration currentConfig = getConfiguration();
-        return changes == CONFIG_UI_MODE && (isInVrUiMode(currentConfig)
-            != isInVrUiMode(lastReportedConfig));
+        return changes == CONFIG_UI_MODE && (AppCompatUtils.isInVrUiMode(currentConfig)
+            != AppCompatUtils.isInVrUiMode(lastReportedConfig));
     }
 
     /**
@@ -10574,10 +10240,6 @@
         return r;
     }
 
-    private static boolean isInVrUiMode(Configuration config) {
-        return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
-    }
-
     private static boolean isInDeskUiMode(Configuration config) {
         return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
     }
@@ -10801,16 +10463,19 @@
                 mAppCompatController.getAppCompatCameraOverrides()
                         .shouldRefreshActivityViaPauseForCameraCompat());
         proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
-                mLetterboxUiController.shouldOverrideMinAspectRatio());
+                mAppCompatController.getAppCompatAspectRatioOverrides()
+                        .shouldOverrideMinAspectRatio());
         proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
                 mAppCompatController.getAppCompatOrientationOverrides()
                         .shouldIgnoreOrientationRequestLoop());
         proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
                 mLetterboxUiController.shouldOverrideForceResizeApp());
         proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
-                mLetterboxUiController.shouldEnableUserAspectRatioSettings());
+                mAppCompatController.getAppCompatAspectRatioOverrides()
+                        .shouldEnableUserAspectRatioSettings());
         proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
-                mLetterboxUiController.isUserFullscreenOverrideEnabled());
+                mAppCompatController.getAppCompatAspectRatioOverrides()
+                        .isUserFullscreenOverrideEnabled());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
new file mode 100644
index 0000000..cf008e7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2024 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.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.utils.OptPropFactory;
+
+import java.util.function.Function;
+
+/**
+ * Encapsulates app compat configurations and overrides related to aspect ratio.
+ */
+class AppCompatAspectRatioOverrides {
+
+    private static final String TAG =
+            TAG_WITH_CLASS_NAME ? "AppCompatAspectRatioOverrides" : TAG_ATM;
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final LetterboxConfiguration mLetterboxConfiguration;
+    @NonNull
+    private final UserAspectRatioState mUserAspectRatioState;
+
+    @NonNull
+    private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
+    @NonNull
+    private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp;
+    @NonNull
+    private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;
+    @NonNull
+    private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
+    @NonNull
+    private final Function<Boolean, Boolean> mIsDisplayFullScreenAndInPostureProvider;
+    @NonNull
+    private final Function<Configuration, Float> mGetHorizontalPositionMultiplierProvider;
+
+    AppCompatAspectRatioOverrides(@NonNull ActivityRecord activityRecord,
+            @NonNull LetterboxConfiguration letterboxConfiguration,
+            @NonNull OptPropFactory optPropBuilder,
+            @NonNull Function<Boolean, Boolean> isDisplayFullScreenAndInPostureProvider,
+            @NonNull Function<Configuration, Float> getHorizontalPositionMultiplierProvider) {
+        mActivityRecord = activityRecord;
+        mLetterboxConfiguration = letterboxConfiguration;
+        mUserAspectRatioState = new UserAspectRatioState();
+        mIsDisplayFullScreenAndInPostureProvider = isDisplayFullScreenAndInPostureProvider;
+        mGetHorizontalPositionMultiplierProvider = getHorizontalPositionMultiplierProvider;
+        mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
+                PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+        mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create(
+                PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE,
+                mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled);
+        mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create(
+                PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled);
+        mAllowOrientationOverrideOptProp = optPropBuilder.create(
+                PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+    }
+
+    /**
+     * Whether we should apply the min aspect ratio per-app override. When this override is applied
+     * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
+     * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
+     * apply if no value is provided in the manifest.
+     *
+     * <p>This method returns {@code true} when the following conditions are met:
+     * <ul>
+     *     <li>Opt-out component property isn't enabled
+     *     <li>Per-app override is enabled
+     * </ul>
+     */
+    boolean shouldOverrideMinAspectRatio() {
+        return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
+                isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO));
+    }
+
+    /**
+     * Whether we should apply the user aspect ratio override to the min aspect ratio for the
+     * current app.
+     */
+    boolean shouldApplyUserMinAspectRatioOverride() {
+        if (!shouldEnableUserAspectRatioSettings()) {
+            return false;
+        }
+
+        mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+        return mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+                && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
+                && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+    }
+
+    boolean shouldApplyUserFullscreenOverride() {
+        if (isUserFullscreenOverrideEnabled()) {
+            mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+            return mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+        }
+
+        return false;
+    }
+
+    boolean isUserFullscreenOverrideEnabled() {
+        if (mAllowUserAspectRatioOverrideOptProp.isFalse()
+                || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse()
+                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean isSystemOverrideToFullscreenEnabled() {
+        return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
+                && !mAllowOrientationOverrideOptProp.isFalse()
+                && (mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+                || mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
+    }
+
+    /**
+     * 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 !mAllowUserAspectRatioOverrideOptProp.isFalse()
+                && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                && mActivityRecord.mDisplayContent != null
+                && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
+    }
+
+    boolean hasFullscreenOverride() {
+        // `mUserAspectRatio` is always initialized first in `shouldApplyUserFullscreenOverride()`.
+        return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled();
+    }
+
+    float getUserMinAspectRatio() {
+        switch (mUserAspectRatioState.mUserAspectRatio) {
+            case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+                return getDisplaySizeMinAspectRatio();
+            case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+                return getSplitScreenAspectRatio();
+            case USER_MIN_ASPECT_RATIO_16_9:
+                return 16 / 9f;
+            case USER_MIN_ASPECT_RATIO_4_3:
+                return 4 / 3f;
+            case USER_MIN_ASPECT_RATIO_3_2:
+                return 3 / 2f;
+            default:
+                throw new AssertionError("Unexpected user min aspect ratio override: "
+                        + mUserAspectRatioState.mUserAspectRatio);
+        }
+    }
+
+    float getSplitScreenAspectRatio() {
+        // Getting the same aspect ratio that apps get in split screen.
+        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+        if (displayArea == null) {
+            return getDefaultMinAspectRatioForUnresizableApps();
+        }
+        int dividerWindowWidth =
+                getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
+        int dividerInsets =
+                getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
+        int dividerSize = dividerWindowWidth - dividerInsets * 2;
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+        if (bounds.width() >= bounds.height()) {
+            bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
+            bounds.right = bounds.centerX();
+        } else {
+            bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
+            bounds.bottom = bounds.centerY();
+        }
+        return AppCompatUtils.computeAspectRatio(bounds);
+    }
+
+    float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
+        return shouldUseSplitScreenAspectRatio(parentConfiguration)
+                ? getSplitScreenAspectRatio()
+                : mActivityRecord.shouldCreateCompatDisplayInsets()
+                        ? getDefaultMinAspectRatioForUnresizableApps()
+                        : getDefaultMinAspectRatio();
+    }
+
+    private float getDisplaySizeMinAspectRatio() {
+        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+        if (displayArea == null) {
+            return mActivityRecord.info.getMinAspectRatio();
+        }
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+        return AppCompatUtils.computeAspectRatio(bounds);
+    }
+
+    private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
+        final boolean isBookMode = mIsDisplayFullScreenAndInPostureProvider
+                .apply(/* isTabletop */false);
+        final boolean isNotCenteredHorizontally = mGetHorizontalPositionMultiplierProvider.apply(
+                parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
+        final boolean isTabletopMode = mIsDisplayFullScreenAndInPostureProvider
+                .apply(/* isTabletop */ true);
+        final boolean isLandscape = isFixedOrientationLandscape(
+                mActivityRecord.getOverrideOrientation());
+        final AppCompatCameraOverrides cameraOverrides =
+                mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+        final AppCompatCameraPolicy cameraPolicy =
+                mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
+        // Don't resize to split screen size when in book mode if letterbox position is centered
+        return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
+                || cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
+                && (cameraPolicy != null
+                    && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
+    }
+
+    @VisibleForTesting
+    int getUserMinAspectRatioOverrideCode() {
+        try {
+            return mActivityRecord.mAtmService.getPackageManager()
+                    .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+        }
+        return mUserAspectRatioState.mUserAspectRatio;
+    }
+
+    private float getDefaultMinAspectRatioForUnresizableApps() {
+        if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
+                || mActivityRecord.getDisplayArea() == null) {
+            return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+                    > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+                    ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+                    : getDefaultMinAspectRatio();
+        }
+
+        return getSplitScreenAspectRatio();
+    }
+
+    private float getDefaultMinAspectRatio() {
+        if (mActivityRecord.getDisplayArea() == null
+                || !mLetterboxConfiguration
+                .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
+            return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+        }
+        return getDisplaySizeMinAspectRatio();
+    }
+
+    private static class UserAspectRatioState {
+        // TODO(b/315140179): Make mUserAspectRatio final
+        // The min aspect ratio override set by user
+        @PackageManager.UserMinAspectRatio
+        private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
+    }
+
+    private boolean isCompatChangeEnabled(long overrideChangeId) {
+        return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+    }
+
+    private Resources getResources() {
+        return mActivityRecord.mWmService.mContext.getResources();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
new file mode 100644
index 0000000..a7d2ecc
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2024 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+/**
+ * Encapsulate app compat policy logic related to aspect ratio.
+ */
+class AppCompatAspectRatioPolicy {
+
+    // Rounding tolerance to be used in aspect ratio computations
+    private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final TransparentPolicy mTransparentPolicy;
+    @NonNull
+    private final AppCompatOverrides mAppCompatOverrides;
+    @NonNull
+    private final AppCompatAspectRatioState mAppCompatAspectRatioState;
+
+    AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord,
+            @NonNull TransparentPolicy transparentPolicy,
+            @NonNull AppCompatOverrides appCompatOverrides) {
+        mActivityRecord = activityRecord;
+        mTransparentPolicy = transparentPolicy;
+        mAppCompatOverrides = appCompatOverrides;
+        mAppCompatAspectRatioState = new AppCompatAspectRatioState();
+    }
+
+    /**
+     * Starts the evaluation of app compat aspect ratio when a new configuration needs to be
+     * resolved.
+     */
+    void reset() {
+        mAppCompatAspectRatioState.reset();
+    }
+
+    float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
+            @NonNull Rect parentBounds) {
+        final float letterboxAspectRatioOverride =
+                mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+                        .getFixedOrientationLetterboxAspectRatio(newParentConfig);
+        // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
+        // be respected in #applyAspectRatio.
+        if (isDefaultMultiWindowLetterboxAspectRatioDesired(newParentConfig)) {
+            return DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+        } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+            return letterboxAspectRatioOverride;
+        }
+        return AppCompatUtils.computeAspectRatio(parentBounds);
+    }
+
+    void applyDesiredAspectRatio(@NonNull Configuration newParentConfig, @NonNull Rect parentBounds,
+            @NonNull Rect resolvedBounds, @NonNull Rect containingBoundsWithInsets,
+            @NonNull Rect containingBounds) {
+        final float desiredAspectRatio = getDesiredAspectRatio(newParentConfig, parentBounds);
+        mAppCompatAspectRatioState.mIsAspectRatioApplied = applyAspectRatio(resolvedBounds,
+                containingBoundsWithInsets, containingBounds, desiredAspectRatio);
+    }
+
+    void applyAspectRatioForLetterbox(Rect outBounds, Rect containingAppBounds,
+            Rect containingBounds) {
+        mAppCompatAspectRatioState.mIsAspectRatioApplied = applyAspectRatio(outBounds,
+                containingAppBounds, containingBounds, 0 /* desiredAspectRatio */);
+    }
+
+    /**
+     * @return {@code true} when an app compat aspect ratio has been applied.
+     */
+    boolean isAspectRatioApplied() {
+        return mAppCompatAspectRatioState.mIsAspectRatioApplied;
+    }
+
+    /**
+     * Returns the min aspect ratio of this activity.
+     */
+    float getMinAspectRatio() {
+        if (mTransparentPolicy.isRunning()) {
+            return mTransparentPolicy.getInheritedMinAspectRatio();
+        }
+        final ActivityInfo info = mActivityRecord.info;
+        if (info.applicationInfo == null) {
+            return info.getMinAspectRatio();
+        }
+        final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+        if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
+            return aspectRatioOverrides.getUserMinAspectRatio();
+        }
+        if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
+                && !mAppCompatOverrides.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera()) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+                && !ActivityInfo.isFixedOrientationPortrait(
+                    mActivityRecord.getOverrideOrientation())) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
+                && isParentFullscreenPortrait()) {
+            // We are using the parent configuration here as this is the most recent one that gets
+            // passed to onConfigurationChanged when a relevant change takes place
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
+            return Math.max(aspectRatioOverrides.getSplitScreenAspectRatio(),
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_SMALL)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE,
+                    info.getMinAspectRatio());
+        }
+        return info.getMinAspectRatio();
+    }
+
+    float getMaxAspectRatio() {
+        if (mTransparentPolicy.isRunning()) {
+            return mTransparentPolicy.getInheritedMaxAspectRatio();
+        }
+        return mActivityRecord.info.getMaxAspectRatio();
+    }
+
+    @Nullable
+    Rect getLetterboxedContainerBounds() {
+        return mAppCompatAspectRatioState.getLetterboxedContainerBounds();
+    }
+
+    /**
+     * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
+     * orientation then aspect ratio restrictions are also already respected.
+     *
+     * <p>This happens when an activity has fixed orientation which doesn't match orientation of the
+     * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link
+     * WindowManagerService#getIgnoreOrientationRequest} for more context.
+     */
+    boolean isLetterboxedForFixedOrientationAndAspectRatio() {
+        return mAppCompatAspectRatioState.isLetterboxedForFixedOrientationAndAspectRatio();
+    }
+
+    boolean isLetterboxedForAspectRatioOnly() {
+        return mAppCompatAspectRatioState.isLetterboxedForAspectRatioOnly();
+    }
+
+    void setLetterboxBoundsForFixedOrientationAndAspectRatio(@NonNull Rect bounds) {
+        mAppCompatAspectRatioState.mLetterboxBoundsForFixedOrientationAndAspectRatio = bounds;
+    }
+
+    void setLetterboxBoundsForAspectRatio(@NonNull Rect bounds) {
+        mAppCompatAspectRatioState.mLetterboxBoundsForAspectRatio = bounds;
+    }
+
+    private boolean isParentFullscreenPortrait() {
+        final WindowContainer<?> parent = mActivityRecord.getParent();
+        return parent != null
+                && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+                && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+
+    /**
+     * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
+     * made to outBounds.
+     *
+     * @return {@code true} if aspect ratio restrictions were applied.
+     */
+    private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
+            Rect containingBounds, float desiredAspectRatio) {
+        final float maxAspectRatio = getMaxAspectRatio();
+        final Task rootTask = mActivityRecord.getRootTask();
+        final Task task = mActivityRecord.getTask();
+        final float minAspectRatio = getMinAspectRatio();
+        final TaskFragment organizedTf = mActivityRecord.getOrganizedTaskFragment();
+        float aspectRatioToApply = desiredAspectRatio;
+        if (task == null || rootTask == null
+                || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1)
+                // Don't set aspect ratio if we are in VR mode.
+                || AppCompatUtils.isInVrUiMode(mActivityRecord.getConfiguration())
+                // TODO(b/232898850): Always respect aspect ratio requests.
+                // Don't set aspect ratio for activity in ActivityEmbedding split.
+                || (organizedTf != null && !organizedTf.fillsParent())) {
+            return false;
+        }
+
+        final int containingAppWidth = containingAppBounds.width();
+        final int containingAppHeight = containingAppBounds.height();
+        final float containingRatio = AppCompatUtils.computeAspectRatio(containingAppBounds);
+
+        if (aspectRatioToApply < 1) {
+            aspectRatioToApply = containingRatio;
+        }
+
+        if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) {
+            aspectRatioToApply = maxAspectRatio;
+        } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) {
+            aspectRatioToApply = minAspectRatio;
+        }
+
+        int activityWidth = containingAppWidth;
+        int activityHeight = containingAppHeight;
+
+        if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+            if (containingAppWidth < containingAppHeight) {
+                // Width is the shorter side, so we use that to figure-out what the max. height
+                // should be given the aspect ratio.
+                activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f);
+            } else {
+                // Height is the shorter side, so we use that to figure-out what the max. width
+                // should be given the aspect ratio.
+                activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f);
+            }
+        } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+            boolean adjustWidth;
+            switch (mActivityRecord.getRequestedConfigurationOrientation()) {
+                case ORIENTATION_LANDSCAPE:
+                    // Width should be the longer side for this landscape app, so we use the width
+                    // to figure-out what the max. height should be given the aspect ratio.
+                    adjustWidth = false;
+                    break;
+                case ORIENTATION_PORTRAIT:
+                    // Height should be the longer side for this portrait app, so we use the height
+                    // to figure-out what the max. width should be given the aspect ratio.
+                    adjustWidth = true;
+                    break;
+                default:
+                    // This app doesn't have a preferred orientation, so we keep the length of the
+                    // longer side, and use it to figure-out the length of the shorter side.
+                    if (containingAppWidth < containingAppHeight) {
+                        // Width is the shorter side, so we use the height to figure-out what the
+                        // max. width should be given the aspect ratio.
+                        adjustWidth = true;
+                    } else {
+                        // Height is the shorter side, so we use the width to figure-out what the
+                        // max. height should be given the aspect ratio.
+                        adjustWidth = false;
+                    }
+                    break;
+            }
+            if (adjustWidth) {
+                activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f);
+            } else {
+                activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f);
+            }
+        }
+
+        if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) {
+            // The display matches or is less than the activity aspect ratio, so nothing else to do.
+            return false;
+        }
+
+        // Compute configuration based on max or min supported width and height.
+        // Also account for the insets (e.g. display cutouts, navigation bar), which will be
+        // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out
+        // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise,
+        // the app bounds would end up too small. To achieve this we will also add clippable insets
+        // when the corresponding dimension fully fills the parent
+
+        int right = activityWidth + containingAppBounds.left;
+        int left = containingAppBounds.left;
+        if (right >= containingAppBounds.right) {
+            right = containingBounds.right;
+            left = containingBounds.left;
+        }
+        int bottom = activityHeight + containingAppBounds.top;
+        int top = containingAppBounds.top;
+        if (bottom >= containingAppBounds.bottom) {
+            bottom = containingBounds.bottom;
+            top = containingBounds.top;
+        }
+        outBounds.set(left, top, right, bottom);
+        return true;
+    }
+
+    /**
+     * Returns {@code true} if the default aspect ratio for a letterboxed app in multi-window mode
+     * should be used.
+     */
+    private boolean isDefaultMultiWindowLetterboxAspectRatioDesired(
+            @NonNull Configuration parentConfig) {
+        final DisplayContent dc = mActivityRecord.mDisplayContent;
+        if (dc == null) {
+            return false;
+        }
+        final int windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+        return WindowConfiguration.inMultiWindowMode(windowingMode)
+                && !dc.getIgnoreOrientationRequest();
+    }
+
+    private static class AppCompatAspectRatioState {
+        // Whether the aspect ratio restrictions applied to the activity bounds
+        // in applyAspectRatio().
+        private boolean mIsAspectRatioApplied = false;
+
+        // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+        // aspect ratio. If not null, they are used as parent container in
+        // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+        @Nullable
+        private Rect mLetterboxBoundsForAspectRatio;
+        // Bounds populated in resolveFixedOrientationConfiguration when this activity is
+        // letterboxed for fixed orientation. If not null, they are used as parent container in
+        // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets. If
+        // letterboxed due to fixed orientation then aspect ratio restrictions are also respected.
+        // This happens when an activity has fixed orientation which doesn't match orientation of
+        // the parent because a display is ignoring orientation request or fixed to user rotation.
+        // See WindowManagerService#getIgnoreOrientationRequest and
+        // WindowManagerService#getFixedToUserRotation for more context.
+        @Nullable
+        private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+
+        @Nullable
+        Rect getLetterboxedContainerBounds() {
+            return mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+                    ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+                    : mLetterboxBoundsForAspectRatio;
+        }
+
+        void reset() {
+            mIsAspectRatioApplied = false;
+            mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+            mLetterboxBoundsForAspectRatio = null;
+        }
+
+        boolean isLetterboxedForFixedOrientationAndAspectRatio() {
+            return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
+        }
+
+        boolean isLetterboxedForAspectRatioOnly() {
+            return mLetterboxBoundsForAspectRatio != null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 16d3787..3eed96d 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -33,6 +33,8 @@
     @NonNull
     private final AppCompatOrientationPolicy mOrientationPolicy;
     @NonNull
+    private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
+    @NonNull
     private final AppCompatOverrides mAppCompatOverrides;
 
     AppCompatController(@NonNull WindowManagerService wmService,
@@ -45,12 +47,9 @@
                 wmService.mLetterboxConfiguration);
         mAppCompatOverrides = new AppCompatOverrides(activityRecord,
                 wmService.mLetterboxConfiguration, optPropBuilder);
-        // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with aspectRatio.
-        final LetterboxUiController tmpController = activityRecord.mLetterboxUiController;
-        mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord,
-                mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
-                tmpController::shouldApplyUserMinAspectRatioOverride,
-                tmpController::isSystemOverrideToFullscreenEnabled);
+        mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
+        mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
+                mTransparentPolicy, mAppCompatOverrides);
     }
 
     @NonNull
@@ -64,6 +63,11 @@
     }
 
     @NonNull
+    AppCompatAspectRatioPolicy getAppCompatAspectRatioPolicy() {
+        return mAppCompatAspectRatioPolicy;
+    }
+
+    @NonNull
     AppCompatOverrides getAppCompatOverrides() {
         return mAppCompatOverrides;
     }
@@ -78,6 +82,11 @@
         return mAppCompatOverrides.getAppCompatCameraOverrides();
     }
 
+    @NonNull
+    AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
+        return mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+    }
+
     @Nullable
     AppCompatCameraPolicy getAppCompatCameraPolicy() {
         if (mActivityRecord.mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index 155e246..9bf8011 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -50,7 +50,6 @@
     private final ActivityRecord mActivityRecord;
     @NonNull
     private final AppCompatCameraOverrides mAppCompatCameraOverrides;
-
     @NonNull
     private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
     @NonNull
@@ -109,7 +108,8 @@
         mOrientationOverridesState.updateOrientationRequestLoopState();
 
         return mOrientationOverridesState.shouldIgnoreRequestInLoop()
-                && !mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio();
+                && !mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForFixedOrientationAndAspectRatio();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 69ba59b..17f0d97 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -32,8 +32,6 @@
 import android.content.pm.ActivityInfo;
 import android.util.Slog;
 
-import java.util.function.BooleanSupplier;
-
 /**
  * Contains all the logic related to orientation in the context of app compatibility
  */
@@ -46,24 +44,11 @@
 
     @NonNull
     private final AppCompatOverrides mAppCompatOverrides;
-    @NonNull
-    private final BooleanSupplier mShouldApplyUserFullscreenOverride;
-    @NonNull
-    private final BooleanSupplier mShouldApplyUserMinAspectRatioOverride;
-    @NonNull
-    private final BooleanSupplier mIsSystemOverrideToFullscreenEnabled;
 
-    // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with spectRatio component
     AppCompatOrientationPolicy(@NonNull ActivityRecord activityRecord,
-                               @NonNull AppCompatOverrides appCompatOverrides,
-                               @NonNull BooleanSupplier shouldApplyUserFullscreenOverride,
-                               @NonNull BooleanSupplier shouldApplyUserMinAspectRatioOverride,
-                               @NonNull BooleanSupplier isSystemOverrideToFullscreenEnabled) {
+                               @NonNull AppCompatOverrides appCompatOverrides) {
         mActivityRecord = activityRecord;
         mAppCompatOverrides = appCompatOverrides;
-        mShouldApplyUserFullscreenOverride = shouldApplyUserFullscreenOverride;
-        mShouldApplyUserMinAspectRatioOverride = shouldApplyUserMinAspectRatioOverride;
-        mIsSystemOverrideToFullscreenEnabled = isSystemOverrideToFullscreenEnabled;
     }
 
     @ActivityInfo.ScreenOrientation
@@ -71,7 +56,9 @@
         final DisplayContent displayContent = mActivityRecord.mDisplayContent;
         final boolean isIgnoreOrientationRequestEnabled = displayContent != null
                 && displayContent.getIgnoreOrientationRequest();
-        if (mShouldApplyUserFullscreenOverride.getAsBoolean() && isIgnoreOrientationRequestEnabled
+        final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
+                .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
+        if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
                 // Do not override orientation to fullscreen for camera activities.
                 // Fixed-orientation activities are rarely tested in other orientations, and it
                 // often results in sideways or stretched previews. As the camera compat treatment
@@ -88,8 +75,9 @@
         // In some cases (e.g. Kids app) we need to map the candidate orientation to some other
         // orientation.
         candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
-
-        if (mShouldApplyUserMinAspectRatioOverride.getAsBoolean() && (!isFixedOrientation(candidate)
+        final boolean shouldApplyUserMinAspectRatioOverride = mAppCompatOverrides
+                .getAppCompatAspectRatioOverrides().shouldApplyUserMinAspectRatioOverride();
+        if (shouldApplyUserMinAspectRatioOverride && (!isFixedOrientation(candidate)
                 || candidate == SCREEN_ORIENTATION_LOCKED)) {
             Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
                     + " for " + mActivityRecord + " is overridden to "
@@ -113,7 +101,9 @@
         // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
         // which will always come first before this check as user override > device
         // manufacturer override.
-        if (mIsSystemOverrideToFullscreenEnabled.getAsBoolean() && isIgnoreOrientationRequestEnabled
+        final boolean isSystemOverrideToFullscreenEnabled = mAppCompatOverrides
+                .getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
+        if (isSystemOverrideToFullscreenEnabled && isIgnoreOrientationRequestEnabled
                 // Do not override orientation to fullscreen for camera activities.
                 // Fixed-orientation activities are rarely tested in other orientations, and it
                 // often results in sideways or stretched previews. As the camera compat treatment
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 94c6ba9..f6f93f9 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -18,20 +18,13 @@
 
 import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
 import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
 import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -61,17 +54,13 @@
     @NonNull
     private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
     @NonNull
-    private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
-    @NonNull
     private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
     @NonNull
-    private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp;
-    @NonNull
-    private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;
-    @NonNull
     private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
     @NonNull
     private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+    @NonNull
+    private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
 
     AppCompatOverrides(@NonNull ActivityRecord activityRecord,
             @NonNull LetterboxConfiguration letterboxConfiguration,
@@ -83,6 +72,11 @@
                 mLetterboxConfiguration, optPropBuilder);
         mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
                 mLetterboxConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+        // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability.
+        mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
+                mLetterboxConfiguration, optPropBuilder,
+                activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture,
+                activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier);
 
         mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
                 mLetterboxConfiguration::isCompatFakeFocusEnabled);
@@ -101,16 +95,8 @@
                             == ORIENTATION_LANDSCAPE
         );
 
-        mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
-                PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
         mAllowForceResizeOverrideOptProp = optPropBuilder.create(
                 PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
-        mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create(
-                PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE,
-                mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled);
-        mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create(
-                PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
-                mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled);
     }
 
     @NonNull
@@ -123,6 +109,11 @@
         return mAppCompatCameraOverrides;
     }
 
+    @NonNull
+    AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
+        return mAppCompatAspectRatioOverrides;
+    }
+
     /**
      * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
      * because some game engines wait to get focus before drawing the content of the app which isn't
@@ -140,34 +131,10 @@
                 isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS));
     }
 
-    boolean isSystemOverrideToFullscreenEnabled(int userAspectRatio) {
-        return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
-                && !mAllowOrientationOverrideOptProp.isFalse()
-                && (userAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
-                    || userAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
-    }
-
     boolean isAllowOrientationOverrideOptOut() {
         return mAllowOrientationOverrideOptProp.isFalse();
     }
 
-    /**
-     * Whether we should apply the min aspect ratio per-app override. When this override is applied
-     * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
-     * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
-     * apply if no value is provided in the manifest.
-     *
-     * <p>This method returns {@code true} when the following conditions are met:
-     * <ul>
-     *     <li>Opt-out component property isn't enabled
-     *     <li>Per-app override is enabled
-     * </ul>
-     */
-    boolean shouldOverrideMinAspectRatio() {
-        return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
-                isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO));
-    }
-
     boolean isOverrideRespectRequestedOrientationEnabled() {
         return isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
     }
@@ -209,28 +176,6 @@
     }
 
     /**
-     * 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 !mAllowUserAspectRatioOverrideOptProp.isFalse()
-                && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
-                && mActivityRecord.mDisplayContent != null
-                && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
-    }
-
-    boolean isUserFullscreenOverrideEnabled() {
-        if (mAllowUserAspectRatioOverrideOptProp.isFalse()
-                || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse()
-                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
      * Whether we should apply the force non resize per-app override. When this override is applied
      * it forces the packages it is applied to to be non-resizable.
      *
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index be51dd3b..1b30a20 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -16,7 +16,12 @@
 
 package com.android.server.wm;
 
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
+
 import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.graphics.Rect;
 
 import java.util.function.BooleanSupplier;
 
@@ -48,4 +53,24 @@
             }
         };
     }
+
+    /**
+     * Returns the aspect ratio of the given {@code rect}.
+     */
+    static float computeAspectRatio(Rect rect) {
+        final int width = rect.width();
+        final int height = rect.height();
+        if (width == 0 || height == 0) {
+            return 0;
+        }
+        return Math.max(width, height) / (float) Math.min(width, height);
+    }
+
+    /**
+     * @param config The current {@link Configuration}
+     * @return {@code true} if using a VR headset.
+     */
+    static boolean isInVrUiMode(Configuration config) {
+        return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index a4fb959..3f4bda7 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1659,6 +1659,8 @@
                     activityName = "";
                 }
                 writeBalAllowedLog(activityName, finalVerdict.getCode(), state);
+            } else {
+                writeBalAllowedLogMinimal(state);
             }
         } else {
             @BalCode int code = finalVerdict.getCode();
@@ -1723,6 +1725,24 @@
         );
     }
 
+    @VisibleForTesting void writeBalAllowedLogMinimal(BalState state) {
+        FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
+                "",
+                BAL_ALLOW_DEFAULT,
+                NO_PROCESS_UID,
+                NO_PROCESS_UID,
+                state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
+                state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
+                state.callerExplicitOptInOrOut(),
+                state.mResultForRealCaller == null ? BAL_BLOCK
+                        : state.mResultForRealCaller.getRawCode(),
+                state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
+                state.realCallerExplicitOptInOrOut(),
+                getTargetSdk(state.mCallingPackage),
+                getTargetSdk(state.mRealCallingPackage)
+        );
+    }
+
     /**
      * Called whenever an activity finishes. Stores the record, so it can be used by ASM grace
      * period checks.
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index efd5202..a38ac9b 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -22,14 +22,23 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
 import static android.app.WindowConfiguration.windowingModeToString;
 import static android.app.WindowConfigurationProto.WINDOWING_MODE;
 import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
 import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
@@ -38,11 +47,14 @@
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.LocaleList;
+import android.util.DisplayMetrics;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -173,6 +185,111 @@
         mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
     }
 
+    /**
+     * If necessary, override configuration fields related to app bounds.
+     * This will happen when the app is targeting SDK earlier than 35.
+     * The insets and configuration has decoupled since SDK level 35, to make the system
+     * compatible to existing apps, override the configuration with legacy metrics. In legacy
+     * metrics, fields such as appBounds will exclude some of the system bar areas.
+     * The override contains all potentially affected fields in Configuration, including
+     * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+     * All overrides to those fields should be in this method.
+     *
+     * TODO: Consider integrate this with computeConfigByResolveHint()
+     */
+    static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
+            Configuration newParentConfiguration, Configuration inOutConfig,
+            boolean optOutEdgeToEdge, boolean hasFixedRotationTransform,
+            boolean hasCompatDisplayInsets) {
+        if (displayContent == null) {
+            return;
+        }
+        final boolean useOverrideInsetsForConfig =
+                displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration
+                        ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+                                && !appInfo.isChangeEnabled(
+                                        OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
+                        : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
+        final int parentWindowingMode =
+                newParentConfiguration.windowConfiguration.getWindowingMode();
+        final boolean isFloating = isFloating(parentWindowingMode)
+                // Check the requested windowing mode of activity as well in case it is
+                // switching between PiP and fullscreen.
+                && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
+                        || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
+        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+        int rotation = newParentConfiguration.windowConfiguration.getRotation();
+        if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
+            rotation = displayContent.getRotation();
+        }
+        if (!optOutEdgeToEdge && (!useOverrideInsetsForConfig
+                || hasCompatDisplayInsets
+                || isFloating
+                || rotation == ROTATION_UNDEFINED)) {
+            // If the insets configuration decoupled logic is not enabled for the app, or the app
+            // already has a compat override, or the context doesn't contain enough info to
+            // calculate the override, skip the override.
+            return;
+        }
+        // Make sure the orientation related fields will be updated by the override insets, because
+        // fixed rotation has assigned the fields from display's configuration.
+        if (hasFixedRotationTransform) {
+            inOutConfig.windowConfiguration.setAppBounds(null);
+            inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+            inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+            inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+            inOutConfig.orientation = ORIENTATION_UNDEFINED;
+        }
+
+        // Override starts here.
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+        final int dw = rotated
+                ? displayContent.mBaseDisplayHeight
+                : displayContent.mBaseDisplayWidth;
+        final int dh = rotated
+                ? displayContent.mBaseDisplayWidth
+                : displayContent.mBaseDisplayHeight;
+        final Rect nonDecorFrame = displayContent.getDisplayPolicy()
+                .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorFrame;
+        // This should be the only place override the configuration for ActivityRecord. Override
+        // the value if not calculated yet.
+        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (outAppBounds == null || outAppBounds.isEmpty()) {
+            inOutConfig.windowConfiguration.setAppBounds(parentBounds);
+            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+            outAppBounds.intersect(nonDecorFrame);
+        }
+        float density = inOutConfig.densityDpi;
+        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+            density = newParentConfiguration.densityDpi;
+        }
+        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+            inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+        }
+        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+            inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+        }
+        if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // For the case of PIP transition and multi-window environment, the
+            // smallestScreenWidthDp is handled already. Override only if the app is in
+            // fullscreen.
+            final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
+            displayContent.computeSizeRanges(info, rotated, dw, dh,
+                    displayContent.getDisplayMetrics().density,
+                    inOutConfig, true /* overrideConfig */);
+        }
+
+        // It's possible that screen size will be considered in different orientation with or
+        // without considering the system bar insets. Override orientation as well.
+        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+                    ? ORIENTATION_PORTRAIT
+                    : ORIENTATION_LANDSCAPE;
+        }
+    }
+
     /** Returns {@code true} if requested override override configuration is not empty. */
     boolean hasRequestedOverrideConfiguration() {
         return mHasOverrideConfiguration;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index b5af806..0161ae5 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,7 +19,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.DimenRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,11 +28,11 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.utils.DimenPxIntSupplier;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Function;
-import java.util.function.IntSupplier;
 
 /** Reads letterbox configs from resources and controls their overrides at runtime. */
 final class LetterboxConfiguration {
@@ -308,34 +307,6 @@
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
     @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
-    // Cached version of IntSupplier customised to evaluate new dimen in pixels
-    // when density changes
-    private static class DimenPxIntSupplier implements IntSupplier {
-
-        @NonNull
-        private final Context mContext;
-
-        private final int mResourceId;
-
-        private float mLastDensity = Float.MIN_VALUE;
-        private int mValue = 0;
-
-        private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
-            mContext = context;
-            mResourceId = resourceId;
-        }
-
-        @Override
-        public int getAsInt() {
-            final float newDensity = mContext.getResources().getDisplayMetrics().density;
-            if (newDensity != mLastDensity) {
-                mLastDensity = newDensity;
-                mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
-            }
-            return mValue;
-        }
-    }
-
     LetterboxConfiguration(@NonNull final Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a3550bc..235d6cd 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,15 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -44,7 +35,6 @@
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
-import static com.android.server.wm.ActivityRecord.computeAspectRatio;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
@@ -54,23 +44,18 @@
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
-import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.TaskDescription;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.RemoteException;
 import android.util.Slog;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -80,7 +65,6 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
@@ -103,11 +87,6 @@
 
     private boolean mShowWallpaperForLetterboxBackground;
 
-    // TODO(b/315140179): Make mUserAspectRatio final
-    // The min aspect ratio override set by user
-    @PackageManager.UserMinAspectRatio
-    private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
-
     @Nullable
     private Letterbox mLetterbox;
 
@@ -154,22 +133,6 @@
     }
 
     /**
-     * Whether we should apply the min aspect ratio per-app override. When this override is applied
-     * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
-     * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
-     * apply if no value is provided in the manifest.
-     *
-     * <p>This method returns {@code true} when the following conditions are met:
-     * <ul>
-     *     <li>Opt-out component property isn't enabled
-     *     <li>Per-app override is enabled
-     * </ul>
-     */
-    boolean shouldOverrideMinAspectRatio() {
-        return getAppCompatOverrides().shouldOverrideMinAspectRatio();
-    }
-
-    /**
      * Whether we should apply the force resize per-app override. When this override is applied it
      * forces the packages it is applied to to be resizable. It won't change whether the app can be
      * put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -230,10 +193,6 @@
         return getAppCompatOverrides().shouldUseDisplayLandscapeNaturalOrientation();
     }
 
-    private boolean isCompatChangeEnabled(long overrideChangeId) {
-        return mActivityRecord.info.isChangeEnabled(overrideChangeId);
-    }
-
     boolean hasWallpaperBackgroundForLetterbox() {
         return mShowWallpaperForLetterboxBackground;
     }
@@ -395,7 +354,7 @@
     // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
     // actually fullscreen. If display is still in transition e.g. unfolding, don't return true
     // for HALF_FOLDED state or app will flicker.
-    private boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
+    boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
         Task task = mActivityRecord.getTask();
         return mActivityRecord.mDisplayContent != null && task != null
                 && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(
@@ -444,48 +403,14 @@
     }
 
     float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
-        return shouldUseSplitScreenAspectRatio(parentConfiguration)
-                ? getSplitScreenAspectRatio()
-                : mActivityRecord.shouldCreateCompatDisplayInsets()
-                        ? getDefaultMinAspectRatioForUnresizableApps()
-                        : getDefaultMinAspectRatio();
+        return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .getFixedOrientationLetterboxAspectRatio(parentConfiguration);
     }
 
     boolean isLetterboxEducationEnabled() {
         return mLetterboxConfiguration.getIsEducationEnabled();
     }
 
-    private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
-        final boolean isBookMode = isDisplayFullScreenAndInPosture(/* isTabletop */ false);
-        final boolean isNotCenteredHorizontally = getHorizontalPositionMultiplier(
-                parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
-        final boolean isTabletopMode = isDisplayFullScreenAndInPosture(/* isTabletop */ true);
-        final boolean isLandscape = isFixedOrientationLandscape(
-                mActivityRecord.getOverrideOrientation());
-        final AppCompatCameraOverrides appCompatCameraOverrides =
-                mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
-        final AppCompatCameraPolicy cameraPolicy =
-                mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
-        final boolean isCameraCompatTreatmentActive = cameraPolicy != null
-                && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
-        // Don't resize to split screen size when in book mode if letterbox position is centered
-        return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
-                    || (appCompatCameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
-                    && isCameraCompatTreatmentActive);
-    }
-
-    private float getDefaultMinAspectRatioForUnresizableApps() {
-        if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
-                || mActivityRecord.getDisplayArea() == null) {
-            return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
-                    > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
-                            ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
-                            : getDefaultMinAspectRatio();
-        }
-
-        return getSplitScreenAspectRatio();
-    }
-
     /**
      * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
      */
@@ -547,123 +472,9 @@
         return !isHorizontalThinLetterboxed();
     }
 
-    float getSplitScreenAspectRatio() {
-        // Getting the same aspect ratio that apps get in split screen.
-        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
-        if (displayArea == null) {
-            return getDefaultMinAspectRatioForUnresizableApps();
-        }
-        int dividerWindowWidth =
-                getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
-        int dividerInsets =
-                getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
-        int dividerSize = dividerWindowWidth - dividerInsets * 2;
-        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
-        if (bounds.width() >= bounds.height()) {
-            bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
-            bounds.right = bounds.centerX();
-        } else {
-            bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
-            bounds.bottom = bounds.centerY();
-        }
-        return computeAspectRatio(bounds);
-    }
-
-    /**
-     * Whether we should enable users to resize the current app.
-     */
-    boolean shouldEnableUserAspectRatioSettings() {
-        return getAppCompatOverrides().shouldEnableUserAspectRatioSettings();
-    }
-
-    /**
-     * Whether we should apply the user aspect ratio override to the min aspect ratio for the
-     * current app.
-     */
-    boolean shouldApplyUserMinAspectRatioOverride() {
-        if (!shouldEnableUserAspectRatioSettings()) {
-            return false;
-        }
-
-        mUserAspectRatio = getUserMinAspectRatioOverrideCode();
-
-        return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
-                && mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
-                && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
-    }
-
-    boolean shouldApplyUserFullscreenOverride() {
-        if (isUserFullscreenOverrideEnabled()) {
-            mUserAspectRatio = getUserMinAspectRatioOverrideCode();
-
-            return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
-        }
-
-        return false;
-    }
-
-    boolean isUserFullscreenOverrideEnabled() {
-        return getAppCompatOverrides().isUserFullscreenOverrideEnabled();
-    }
-
-    boolean isSystemOverrideToFullscreenEnabled() {
-        return getAppCompatOverrides().isSystemOverrideToFullscreenEnabled(mUserAspectRatio);
-    }
-
-    boolean hasFullscreenOverride() {
-        // `mUserAspectRatio` is always initialized first in `shouldApplyUserFullscreenOverride()`.
-        return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled();
-    }
-
-    float getUserMinAspectRatio() {
-        switch (mUserAspectRatio) {
-            case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
-                return getDisplaySizeMinAspectRatio();
-            case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
-                return getSplitScreenAspectRatio();
-            case USER_MIN_ASPECT_RATIO_16_9:
-                return 16 / 9f;
-            case USER_MIN_ASPECT_RATIO_4_3:
-                return 4 / 3f;
-            case USER_MIN_ASPECT_RATIO_3_2:
-                return 3 / 2f;
-            default:
-                throw new AssertionError("Unexpected user min aspect ratio override: "
-                        + mUserAspectRatio);
-        }
-    }
-
-    @VisibleForTesting
-    int getUserMinAspectRatioOverrideCode() {
-        try {
-            return mActivityRecord.mAtmService.getPackageManager()
-                    .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
-        }
-        return mUserAspectRatio;
-    }
-
-    private float getDisplaySizeMinAspectRatio() {
-        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
-        if (displayArea == null) {
-            return mActivityRecord.info.getMinAspectRatio();
-        }
-        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
-        return computeAspectRatio(bounds);
-    }
-
-    private float getDefaultMinAspectRatio() {
-        if (mActivityRecord.getDisplayArea() == null
-                || !mLetterboxConfiguration
-                    .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
-            return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
-        }
-        return getDisplaySizeMinAspectRatio();
-    }
-
-    Resources getResources() {
-        return mActivityRecord.mWmService.mContext.getResources();
+    boolean shouldOverrideMinAspectRatio() {
+        return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio();
     }
 
     @LetterboxConfiguration.LetterboxVerticalReachabilityPosition
@@ -1086,7 +897,7 @@
 
         pw.println(prefix + "  letterboxReason=" + getLetterboxReasonString(mainWin));
         pw.println(prefix + "  activityAspectRatio="
-                + mActivityRecord.computeAspectRatio(mActivityRecord.getBounds()));
+                + AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds()));
 
         boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin);
         pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi);
@@ -1145,13 +956,15 @@
         if (mActivityRecord.inSizeCompatMode()) {
             return "SIZE_COMPAT_MODE";
         }
-        if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) {
+        if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()) {
             return "FIXED_ORIENTATION";
         }
         if (mainWin.isLetterboxedForDisplayCutout()) {
             return "DISPLAY_CUTOUT";
         }
-        if (mActivityRecord.isLetterboxedForAspectRatioOnly()) {
+        if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForAspectRatioOnly()) {
             return "ASPECT_RATIO";
         }
         return "UNKNOWN_REASON";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 79fec23..74f8a068 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -816,42 +816,30 @@
         }
     }
 
-    /** Convenience method to reparent a task to the top or bottom position of the root task. */
-    boolean reparent(Task preferredRootTask, boolean toTop,
-            @ReparentMoveRootTaskMode int moveRootTaskMode, boolean animate, boolean deferResume,
-            String reason) {
-        return reparent(preferredRootTask, toTop ? MAX_VALUE : 0, moveRootTaskMode, animate,
-                deferResume, true /* schedulePictureInPictureModeChange */, reason);
-    }
-
     /**
      * Reparents the task into a preferred root task, creating it if necessary.
      *
      * @param preferredRootTask the target root task to move this task
-     * @param position the position to place this task in the new root task
+     * @param toTop top or bottom position to place this task in the target task
      * @param animate whether or not we should wait for the new window created as a part of the
      *            reparenting to be drawn and animated in
      * @param moveRootTaskMode whether or not to move the root task to the front always, only if
      *            it was previously focused & in front, or never
      * @param deferResume whether or not to update the visibility of other tasks and root tasks
      *            that may have changed as a result of this reparenting
-     * @param schedulePictureInPictureModeChange specifies whether or not to schedule the PiP mode
-     *            change. Callers may set this to false if they are explicitly scheduling PiP mode
-     *            changes themselves, like during the PiP animation
      * @param reason the caller of this reparenting
      * @return whether the task was reparented
      */
     // TODO: Inspect all call sites and change to just changing windowing mode of the root task vs.
     // re-parenting the task. Can only be done when we are no longer using static root task Ids.
-    boolean reparent(Task preferredRootTask, int position,
+    boolean reparent(Task preferredRootTask, boolean toTop,
             @ReparentMoveRootTaskMode int moveRootTaskMode, boolean animate, boolean deferResume,
-            boolean schedulePictureInPictureModeChange, String reason) {
+            String reason) {
         final ActivityTaskSupervisor supervisor = mTaskSupervisor;
         final RootWindowContainer root = mRootWindowContainer;
-        final WindowManagerService windowManager = mAtmService.mWindowManager;
         final Task sourceRootTask = getRootTask();
         final Task toRootTask = supervisor.getReparentTargetRootTask(this, preferredRootTask,
-                position == MAX_VALUE);
+                toTop);
         if (toRootTask == sourceRootTask) {
             return false;
         }
@@ -862,7 +850,6 @@
         final ActivityRecord topActivity = getTopNonFinishingActivity();
 
         mAtmService.deferWindowLayout();
-        boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
             final boolean wasFocused = r != null && root.isTopDisplayFocusedRootTask(sourceRootTask)
@@ -878,12 +865,7 @@
                     || (moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT
                             && (wasFocused || wasFront));
 
-            reparent(toRootTask, position, moveRootTaskToFront, reason);
-
-            if (schedulePictureInPictureModeChange) {
-                // Notify of picture-in-picture mode changes
-                supervisor.scheduleUpdatePictureInPictureModeIfNeeded(this, sourceRootTask);
-            }
+            reparent(toRootTask, toTop ? MAX_VALUE : 0, moveRootTaskToFront, reason);
 
             // If the task had focus before (or we're requested to move focus), move focus to the
             // new root task by moving the root task to the front.
@@ -3424,9 +3406,11 @@
         appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.isUserFullscreenOverrideEnabled = top != null
-                && top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
+                && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+                    .shouldApplyUserFullscreenOverride();
         appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top != null
-                && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
+                && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+                    .isSystemOverrideToFullscreenEnabled();
         appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
                 && top.mLetterboxUiController.isFromDoubleTap();
         if (top != null) {
@@ -3460,7 +3444,8 @@
         }
         appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = top != null
                 && !appCompatTaskInfo.topActivityInSizeCompat
-                && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings()
+                && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+                    .shouldEnableUserAspectRatioSettings()
                 && !info.isTopActivityTransparent;
         appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top == null
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 47af6fc..2a3e945 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2757,12 +2757,19 @@
             return out;
         }
 
+        // Get the animation theme from the top-most application window
+        // when Flags.customAnimationsBehindTranslucent() is false.
         final AnimationOptions animOptionsForActivityTransition =
                 calculateAnimationOptionsForActivityTransition(type, sortedTargets);
+
         if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) {
             out.setAnimationOptions(animOptionsForActivityTransition);
         }
 
+        // Store the animation options of the topmost non-translucent change
+        // (Used when Flags.customAnimationsBehindTranslucent() is true)
+        AnimationOptions activityAboveAnimationOptions = null;
+
         final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
@@ -2881,9 +2888,26 @@
                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
             }
 
-            AnimationOptions animOptions = null;
+            // Calculate the animation options for this change
             if (Flags.moveAnimationOptionsToChange()) {
-                if (activityRecord != null && animOptionsForActivityTransition != null) {
+                AnimationOptions animOptions = null;
+                if (Flags.customAnimationsBehindTranslucent() && activityRecord != null) {
+                    if (activityAboveAnimationOptions != null) {
+                        // Inherit the options from one of the changes on top of this
+                        animOptions = activityAboveAnimationOptions;
+                    } else {
+                        // Create the options based on this change's custom animations and layout
+                        // parameters
+                        animOptions = getOptions(activityRecord /* customAnimActivity */,
+                                                 activityRecord /* animLpActivity */);
+                        if (!change.hasFlags(FLAG_TRANSLUCENT)) {
+                            // If this change is not translucent, its options are going to be
+                            // inherited by the changes below
+                            activityAboveAnimationOptions = animOptions;
+                        }
+                    }
+                } else if (activityRecord != null && animOptionsForActivityTransition != null) {
+                    // Use the same options from the top activity for all the activities
                     animOptions = animOptionsForActivityTransition;
                 } else if (Flags.activityEmbeddingOverlayPresentationFlag()
                         && isEmbeddedTaskFragment) {
@@ -2931,25 +2955,42 @@
     @Nullable
     private static AnimationOptions calculateAnimationOptionsForActivityTransition(
             @TransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets) {
-        TransitionInfo.AnimationOptions animOptions = null;
-
-        // Check if the top-most app is an activity (ie. activity->activity). If so, make sure
-        // to honor its custom transition options.
         WindowContainer<?> topApp = null;
         for (int i = 0; i < sortedTargets.size(); i++) {
-            if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
-            topApp = sortedTargets.get(i).mContainer;
-            break;
+            if (!isWallpaper(sortedTargets.get(i).mContainer)) {
+                topApp = sortedTargets.get(i).mContainer;
+                break;
+            }
         }
-        if (topApp.asActivityRecord() != null) {
-            final ActivityRecord topActivity = topApp.asActivityRecord();
-            animOptions = addCustomActivityTransition(topActivity, true/* open */,
-                    null /* animOptions */);
-            animOptions = addCustomActivityTransition(topActivity, false/* open */,
+        ActivityRecord animLpActivity = findAnimLayoutParamsActivityRecord(type, sortedTargets);
+        return getOptions(topApp.asActivityRecord() /* customAnimActivity */,
+                animLpActivity /* animLpActivity */);
+    }
+
+    /**
+     * Updates and returns animOptions with the layout parameters of animLpActivity
+     * @param customAnimActivity the activity that drives the custom animation options
+     * @param animLpActivity the activity that drives the animation options with its layout
+     *                       parameters
+     * @return the options extracted from the provided activities
+     */
+    @Nullable
+    private static AnimationOptions getOptions(@Nullable ActivityRecord customAnimActivity,
+            @Nullable ActivityRecord animLpActivity) {
+        AnimationOptions animOptions = null;
+        // Custom
+        if (customAnimActivity != null) {
+            animOptions = addCustomActivityTransition(customAnimActivity, true /* open */,
+                    animOptions);
+            animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
                     animOptions);
         }
-        final WindowManager.LayoutParams animLp =
-                getLayoutParamsForAnimationsStyle(type, sortedTargets);
+
+        // Layout parameters
+        final WindowState mainWindow = animLpActivity != null
+                ? animLpActivity.findMainWindow() : null;
+        final WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null;
+
         if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
                 && animLp.windowAnimations != 0) {
             // Don't send animation options if no windowAnimations have been set or if the we
@@ -3087,10 +3128,9 @@
         return ancestor;
     }
 
-    private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
-            ArrayList<ChangeInfo> sortedTargets) {
-        // Find the layout params of the top-most application window that is part of the
-        // transition, which is what will control the animation theme.
+    @Nullable
+    private static ActivityRecord findAnimLayoutParamsActivityRecord(
+            @TransitionType int transit, @NonNull List<ChangeInfo> sortedTargets) {
         final ArraySet<Integer> activityTypes = new ArraySet<>();
         final int targetCount = sortedTargets.size();
         for (int i = 0; i < targetCount; ++i) {
@@ -3110,16 +3150,7 @@
             // activity through the layout parameter animation style.
             return null;
         }
-        final ActivityRecord animLpActivity =
-                findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
-        final WindowState mainWindow = animLpActivity != null
-                ? animLpActivity.findMainWindow() : null;
-        return mainWindow != null ? mainWindow.mAttrs : null;
-    }
 
-    private static ActivityRecord findAnimLayoutParamsActivityRecord(
-            List<ChangeInfo> sortedTargets,
-            @TransitionType int transit, ArraySet<Integer> activityTypes) {
         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
                 w -> w.getRemoteAnimationDefinition() != null
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1d02f1c..4da8bbf 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -117,6 +117,7 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.AlwaysTruePredicate;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -458,6 +459,9 @@
         source.setFrame(provider.getArbitraryRectangle())
                 .updateSideHint(getBounds())
                 .setBoundingRects(provider.getBoundingRects());
+        if (Flags.enableCaptionCompatInsetForceConsumption()) {
+            source.setFlags(provider.getFlags());
+        }
         mLocalInsetsSources.put(id, source);
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 60d3e78..bbd9c0a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1655,6 +1655,22 @@
         // Otherwise if other places send wpc.getConfiguration() to client, the configuration may
         // be ignored due to the seq is older.
         resolvedConfig.seq = newParentConfig.seq;
+
+        if (mConfigActivityRecord != null) {
+            // Let the activity decide whether to apply the size override.
+            return;
+        }
+        final DisplayContent displayContent = mAtm.mWindowManager != null
+                ? mAtm.mWindowManager.getDefaultDisplayContentLocked()
+                : null;
+        applySizeOverrideIfNeeded(
+                displayContent,
+                mInfo,
+                newParentConfig,
+                resolvedConfig,
+                false /* optOutEdgeToEdge */,
+                false /* hasFixedRotationTransform */,
+                false /* hasCompatDisplayInsets */);
     }
 
     void dispatchConfiguration(@NonNull Configuration config) {
diff --git a/services/core/java/com/android/server/wm/utils/DimenPxIntSupplier.java b/services/core/java/com/android/server/wm/utils/DimenPxIntSupplier.java
new file mode 100644
index 0000000..5e49345
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/DimenPxIntSupplier.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.utils;
+
+import android.annotation.DimenRes;
+import android.annotation.NonNull;
+import android.content.Context;
+
+import java.util.function.IntSupplier;
+
+/**
+ * Cached version of IntSupplier customised to evaluate new dimen in pixels
+ * when density changes.
+ * @hide
+ */
+public class DimenPxIntSupplier implements IntSupplier {
+
+    @NonNull
+    private final Context mContext;
+
+    private final int mResourceId;
+
+    private float mLastDensity = Float.MIN_VALUE;
+    private int mValue = 0;
+
+    public DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
+        mContext = context;
+        mResourceId = resourceId;
+    }
+
+    @Override
+    public int getAsInt() {
+        final float newDensity = mContext.getResources().getDisplayMetrics().density;
+        if (newDensity != mLastDensity) {
+            mLastDensity = newDensity;
+            mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
+        }
+        return mValue;
+    }
+}
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index a32b0f1..9ec5ae5 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -125,6 +125,9 @@
         case DeviceType::ROTARY_ENCODER:
             ioctl(fd, UI_SET_EVBIT, EV_REL);
             ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
+            if (vd_flags::high_resolution_scroll()) {
+                ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
+            }
             break;
         default:
             ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType));
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1f0c827..eab7364 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -382,6 +382,7 @@
             PointerControllerInterface::ControllerType type) override;
     void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
                                        const FloatPoint& position) override;
+    void notifyMouseCursorFadedOnTyping() override;
 
     /* --- InputFilterPolicyInterface implementation --- */
     void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -788,6 +789,10 @@
             InputReaderConfiguration::Change::DISPLAY_INFO);
 }
 
+void NativeInputManager::notifyMouseCursorFadedOnTyping() {
+    mInputManager->getReader().notifyMouseCursorFadedOnTyping();
+}
+
 void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
                                                           uint32_t lockedModifierState) {
     JNIEnv* env = jniEnv();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e5a1ebf..db4b171 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -381,6 +381,9 @@
                     + "OnDevicePersonalizationSystemService$Lifecycle";
     private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
             "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+    private static final String CRASHRECOVERY_MODULE_LIFECYCLE_CLASS =
+            "com.android.server.crashrecovery.CrashRecoveryModule$Lifecycle";
+
 
     /*
      * Implementation class names and jar locations for services in
@@ -1196,15 +1199,18 @@
         mSystemServiceManager.startService(RecoverySystemService.Lifecycle.class);
         t.traceEnd();
 
-        // Initialize RescueParty.
-        RescueParty.registerHealthObserver(mSystemContext);
-        if (!Flags.recoverabilityDetection()) {
-            // Now that we have the bare essentials of the OS up and running, take
-            // note that we just booted, which might send out a rescue party if
-            // we're stuck in a runtime restart loop.
-            PackageWatchdog.getInstance(mSystemContext).noteBoot();
+        if (!Flags.refactorCrashrecovery()) {
+            // Initialize RescueParty.
+            RescueParty.registerHealthObserver(mSystemContext);
+            if (!Flags.recoverabilityDetection()) {
+                // Now that we have the bare essentials of the OS up and running, take
+                // note that we just booted, which might send out a rescue party if
+                // we're stuck in a runtime restart loop.
+                PackageWatchdog.getInstance(mSystemContext).noteBoot();
+            }
         }
 
+
         // Manages LEDs and display backlight so we need it to bring up the display.
         t.traceBegin("StartLightsService");
         mSystemServiceManager.startService(LightsService.class);
@@ -2931,12 +2937,18 @@
         mPackageManagerService.systemReady();
         t.traceEnd();
 
-        if (Flags.recoverabilityDetection()) {
-            // Now that we have the essential services needed for mitigations, register the boot
-            // with package watchdog.
-            // Note that we just booted, which might send out a rescue party if we're stuck in a
-            // runtime restart loop.
-            PackageWatchdog.getInstance(mSystemContext).noteBoot();
+        if (Flags.refactorCrashrecovery()) {
+            t.traceBegin("StartCrashRecoveryModule");
+            mSystemServiceManager.startService(CRASHRECOVERY_MODULE_LIFECYCLE_CLASS);
+            t.traceEnd();
+        } else {
+            if (Flags.recoverabilityDetection()) {
+                // Now that we have the essential services needed for mitigations, register the boot
+                // with package watchdog.
+                // Note that we just booted, which might send out a rescue party if we're stuck in a
+                // runtime restart loop.
+                PackageWatchdog.getInstance(mSystemContext).noteBoot();
+            }
         }
 
         t.traceBegin("MakeDisplayManagerServiceReady");
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 2029b71..8f630af 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -99,6 +99,12 @@
                     InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
             assertThat(mInputMethodService).isNotNull();
 
+            // The activity gets focus.
+            assertThat(mActivity.hasWindowFocus()).isTrue();
+            assertThat(mInputMethodService.getCurrentInputEditorInfo()).isNotNull();
+            assertThat(mInputMethodService.getCurrentInputEditorInfo().packageName)
+                    .isEqualTo(mTargetPackageName);
+
             // The editor won't bring up keyboard by default.
             assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
             assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index e2f3eec..9ca4f1d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -99,14 +99,8 @@
         listener.onUserCreated(userInfo, /* unused token */ new Object());
         waitForIdle();
 
-        // Assert UserDataRepository contains the expected UserData
-        final var allUserData = collectUserData(repository);
-        assertThat(allUserData).hasSize(1);
-        assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
-
-        // Assert UserDataRepository called the InputMethodBindingController creator function.
-        verify(bindingControllerFactorySpy).apply(ANY_USER_ID);
-        assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID);
+        // Assert UserDataRepository remains to be empty.
+        assertThat(collectUserData(repository)).isEmpty();
     }
 
     @Test
@@ -121,12 +115,15 @@
         final var listener = captor.getValue();
 
         // Add one UserData ...
-        final var userInfo = new UserInfo();
-        userInfo.id = ANY_USER_ID;
-        listener.onUserCreated(userInfo, /* unused token */ new Object());
-        waitForIdle();
+        synchronized (ImfLock.class) {
+            final var userData = repository.getOrCreate(ANY_USER_ID);
+            assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
+        }
+
         // ... and then call onUserRemoved
         assertThat(collectUserData(repository)).hasSize(1);
+        final var userInfo = new UserInfo();
+        userInfo.id = ANY_USER_ID;
         listener.onUserRemoved(userInfo);
         waitForIdle();
 
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
index b706a65..67212b6 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -53,13 +53,14 @@
 
     @Override
     public void onStartInput(EditorInfo info, boolean restarting) {
-        Log.i(TAG, "onStartInput() editor=" + info + ", restarting=" + restarting);
+        Log.i(TAG, "onStartInput() editor=" + dumpEditorInfo(info) + ", restarting=" + restarting);
         super.onStartInput(info, restarting);
     }
 
     @Override
     public void onStartInputView(EditorInfo info, boolean restarting) {
-        Log.i(TAG, "onStartInputView() editor=" + info + ", restarting=" + restarting);
+        Log.i(TAG, "onStartInputView() editor=" + dumpEditorInfo(info)
+                + ", restarting=" + restarting);
         super.onStartInputView(info, restarting);
         mInputViewStarted = true;
         if (mCountDownLatchForTesting != null) {
@@ -99,4 +100,14 @@
             mCountDownLatchForTesting.countDown();
         }
     }
+
+    private String dumpEditorInfo(EditorInfo info) {
+        var sb = new StringBuilder();
+        sb.append("EditorInfo{packageName=").append(info.packageName);
+        sb.append(" fieldId=").append(info.fieldId);
+        sb.append(" hintText=").append(info.hintText);
+        sb.append(" privateImeOptions=").append(info.privateImeOptions);
+        sb.append("}");
+        return sb.toString();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 396f4da..bf7e3a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -525,7 +525,7 @@
 
     @Test
     public void
-    chooseRestorePolicy_flagOnNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsIgnore()
+    chooseRestorePolicy_flagOnNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsAccept()
             throws Exception {
 
         mSetFlagsRule.enableFlags(
@@ -540,7 +540,8 @@
         FileMetadata info = new FileMetadata();
         info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
 
-        PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+        PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+                Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         PackageManagerStub.sPackageInfo = packageInfo;
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -559,7 +560,7 @@
 
     @Test
     public void
-    chooseRestorePolicy_flagOffNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsAccept()
+    chooseRestorePolicy_flagOffNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsIgnore()
             throws Exception {
 
         mSetFlagsRule.disableFlags(
@@ -574,7 +575,8 @@
         FileMetadata info = new FileMetadata();
         info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
 
-        PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+        PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+                Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         PackageManagerStub.sPackageInfo = packageInfo;
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -608,7 +610,8 @@
         FileMetadata info = new FileMetadata();
         info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
 
-        PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+        PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+                Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         PackageManagerStub.sPackageInfo = packageInfo;
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -716,6 +719,36 @@
                 LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
     }
 
+    @Test
+    public void
+    chooseRestorePolicy_allowlistNotSetNotRestoreAnyVersionVersionMismatch_returnsIgnore()
+            throws Exception {
+        mSetFlagsRule.disableFlags(
+                Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+        TarBackupReader tarBackupReader = createTarBackupReader();
+
+        Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+        FileMetadata info = new FileMetadata();
+        info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 2;
+
+        PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+                Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1);
+        PackageManagerStub.sPackageInfo = packageInfo;
+
+        doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+                packageInfo.packageName);
+        RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+                false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId, mContext);
+
+        assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+        assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+                LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
+    }
+
     private TarBackupReader createTarBackupReader() throws Exception {
         InputStream inputStream = mContext.getResources().openRawResource(
                 R.raw.backup_telephony_no_password);
@@ -726,7 +759,7 @@
         return tarBackupReader;
     }
 
-    private PackageInfo createNonRestoreAnyVersionUPackage(){
+    private PackageInfo createNonRestoreAnyVersionPackage(int versionCode) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test";
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -740,7 +773,7 @@
                         SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                         null,
                         null));
-        packageInfo.versionCode = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+        packageInfo.versionCode = versionCode;
         return packageInfo;
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
new file mode 100644
index 0000000..127d3e8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -0,0 +1,58 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_mainline_modularization",
+}
+
+android_test {
+    name: "CrashRecoveryModuleTests",
+
+    srcs: [
+        "*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "mockito-target-extended-minus-junit4",
+        "services.core",
+        "truth",
+        "flag-junit",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidManifest.xml
new file mode 100644
index 0000000..067f116
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 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.server.crashrecovery">
+
+    <uses-sdk android:targetSdkVersion="35" />
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.crashrecovery"
+        android:label="Frameworks crashrecovery module test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
new file mode 100644
index 0000000..7b06ebe
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 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 Crashrecovery Module Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CrashRecoveryModuleTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="CrashRecoveryModuleTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.crashrecovery" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryModuleTest.java b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryModuleTest.java
new file mode 100644
index 0000000..c481f84
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryModuleTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 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.crashrecovery;
+
+import static com.android.server.SystemService.PHASE_ACTIVITY_MANAGER_READY;
+import static com.android.server.SystemService.PHASE_THIRD_PARTY_APPS_CAN_START;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog;
+import com.android.server.RescueParty;
+import com.android.server.crashrecovery.CrashRecoveryModule.Lifecycle;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Test CrashRecoveryModule.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrashRecoveryModuleTest {
+
+    @Rule
+    public SetFlagsRule mSetFlagsRule;
+
+    private MockitoSession mSession;
+    private Lifecycle mLifecycle;
+
+    @Mock PackageWatchdog mPackageWatchdog;
+
+    @Before
+    public void setup() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(PackageWatchdog.class)
+                .mockStatic(RescueParty.class)
+                .startMocking();
+        when(PackageWatchdog.getInstance(context)).thenReturn(mPackageWatchdog);
+        ExtendedMockito.doNothing().when(() -> RescueParty.registerHealthObserver(context));
+        mLifecycle = new Lifecycle(context);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.finishMocking();
+    }
+
+    @Test
+    public void testLifecycleServiceStart() {
+        mLifecycle.onStart();
+
+        verify(mPackageWatchdog, times(1)).noteBoot();
+        ExtendedMockito.verify(() -> RescueParty.registerHealthObserver(any()),
+                Mockito.times(1));
+    }
+
+    @Test
+    public void testLifecycleServiceOnBootPhase() {
+        doNothing().when(mPackageWatchdog).onPackagesReady();
+
+        mLifecycle.onBootPhase(PHASE_ACTIVITY_MANAGER_READY);
+        verify(mPackageWatchdog, never()).onPackagesReady();
+
+        mLifecycle.onBootPhase(PHASE_THIRD_PARTY_APPS_CAN_START);
+        verify(mPackageWatchdog, times(1)).onPackagesReady();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/OWNERS b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/OWNERS
new file mode 100644
index 0000000..8337fd2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index ca30551..62fa951 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -152,6 +152,7 @@
 
     @Test
     public void onSwitchToAnotherUser_userStateClearedNonDefaultValues() {
+        String componentNameString = COMPONENT_NAME.flattenToString();
         mUserState.getBoundServicesLocked().add(mMockConnection);
         mUserState.getBindingServicesLocked().add(COMPONENT_NAME);
         mUserState.setLastSentClientStateLocked(
@@ -162,10 +163,13 @@
         mUserState.setInteractiveUiTimeoutLocked(30);
         mUserState.mEnabledServices.add(COMPONENT_NAME);
         mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
-        mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), HARDWARE);
-        mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), SOFTWARE);
-        mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), GESTURE);
-        mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
+        mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), HARDWARE);
+        mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), SOFTWARE);
+        mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), GESTURE);
+        mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), QUICK_SETTINGS);
+        mUserState.updateA11yTilesInQsPanelLocked(
+                Set.of(AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME));
+        mUserState.setTargetAssignedToAccessibilityButton(componentNameString);
         mUserState.setTouchExplorationEnabledLocked(true);
         mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
         mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
@@ -189,6 +193,8 @@
         assertTrue(mUserState.getShortcutTargetsLocked(HARDWARE).isEmpty());
         assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
         assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
+        assertTrue(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS).isEmpty());
+        assertTrue(mUserState.getA11yQsTilesInQsPanel().isEmpty());
         assertNull(mUserState.getTargetAssignedToAccessibilityButton());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
         assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index dc8d239..0def516 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility
 
+import android.util.MathUtils.sqrt
+
 import android.companion.virtual.VirtualDeviceManager
 import android.companion.virtual.VirtualDeviceParams
 import android.content.Context
@@ -59,6 +61,7 @@
     companion object {
         const val DISPLAY_ID = 1
         const val DEVICE_ID = 123
+        const val MOUSE_POINTER_MOVEMENT_STEP = 1.8f
         // This delay is required for key events to be sent and handled correctly.
         // The handler only performs a move/scroll event if it receives the key event
         // at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
@@ -113,8 +116,7 @@
         Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
         Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
 
-        mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
-            testLooper.looper, DISPLAY_ID)
+        mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
         // VirtualMouse is created on a separate thread.
         // Wait for VirtualMouse to be created before running tests
         TimeUnit.MILLISECONDS.sleep(20L)
@@ -145,7 +147,7 @@
     fun whenMouseDirectionalKeyIsPressed_relativeEventIsSent() {
         // There should be some delay between the downTime of the key event and calling onKeyEvent
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.DOWN_MOVE.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.DIAGONAL_DOWN_LEFT_MOVE.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
 
@@ -153,14 +155,15 @@
         testLooper.dispatchAll()
 
         // Verify the sendRelativeEvent method is called once and capture the arguments
-        verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(1.8f))
+        verifyRelativeEvents(arrayOf(-MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
+            arrayOf(MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
     }
 
     @Test
     fun whenClickKeyIsPressed_buttonEventIsSent() {
         // There should be some delay between the downTime of the key event and calling onKeyEvent
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -179,7 +182,7 @@
     @Test
     fun whenHoldKeyIsPressed_buttonEventIsSent() {
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -195,7 +198,7 @@
     @Test
     fun whenReleaseKeyIsPressed_buttonEventIsSent() {
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -209,18 +212,38 @@
     }
 
     @Test
-    fun whenScrollUpKeyIsPressed_scrollEventIsSent() {
+    fun whenScrollToggleOn_ScrollUpKeyIsPressed_scrollEventIsSent() {
         // There should be some delay between the downTime of the key event and calling onKeyEvent
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+        val keyCodeScrollToggle = MouseKeysInterceptor.MouseKeyEvent.SCROLL_TOGGLE.keyCodeValue
+        val keyCodeScroll = MouseKeysInterceptor.MouseKeyEvent.UP_MOVE_OR_SCROLL.keyCodeValue
+
+        val scrollToggleDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+            keyCodeScrollToggle, 0, 0, DEVICE_ID, 0)
+        val scrollDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+            keyCodeScroll, 0, 0, DEVICE_ID, 0)
+
+        mouseKeysInterceptor.onKeyEvent(scrollToggleDownEvent, 0)
+        mouseKeysInterceptor.onKeyEvent(scrollDownEvent, 0)
+        testLooper.dispatchAll()
+
+        // Verify the sendScrollEvent method is called once and capture the arguments
+        verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+    }
+
+    @Test
+    fun whenScrollToggleOff_DirectionalUpKeyIsPressed_RelativeEventIsSent() {
+        // There should be some delay between the downTime of the key event and calling onKeyEvent
+        val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.UP_MOVE_OR_SCROLL.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
 
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
         testLooper.dispatchAll()
 
-        // Verify the sendScrollEvent method is called once and capture the arguments
-        verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+        // Verify the sendRelativeEvent method is called once and capture the arguments
+        verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(-MOUSE_POINTER_MOVEMENT_STEP))
     }
 
     private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 10f4308..599a3b8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -396,7 +396,7 @@
     }
 
     @Test
-    public void cecDevices_tracking_updatesPhysicalAddress() {
+    public void cecDevices_tracking_updatesPhysicalAddress_add() {
         int logicalAddress = Constants.ADDR_PLAYBACK_1;
         int initialPhysicalAddress = 0x1000;
         int updatedPhysicalAddress = 0x2000;
@@ -415,11 +415,12 @@
         assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress);
         assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
 
-        // ADD for physical address first detected
-        // UPDATE for updating device with new physical address
+        // Handle case where PA is changed: Update CEC device information by calling
+        // addCecDevice().
         assertThat(mDeviceEventListenerStatuses).containsExactly(
                 HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
-                HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+                HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE,
+                HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
     }
 
     @Test
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 e64397d..316b5fa 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
@@ -17,6 +17,7 @@
 package com.android.server.media.projection;
 
 
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
 import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING;
@@ -50,6 +51,7 @@
 
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
@@ -66,7 +68,9 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.ContentRecordingSession;
 import android.view.ContentRecordingSession.RecordContent;
 
@@ -81,6 +85,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -151,6 +156,9 @@
     private ContentRecordingSession mWaitingDisplaySession =
             createDisplaySession(DEFAULT_DISPLAY);
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private ActivityManagerInternal mAmInternal;
     @Mock
@@ -158,6 +166,8 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
     private IMediaProjectionWatcherCallback mWatcherCallback;
     @Mock
     private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@@ -177,6 +187,7 @@
         mContext = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE));
 
         mClock = new OffsettableClock.Stopped();
         mWaitingDisplaySession.setWaitingForConsent(true);
@@ -246,6 +257,39 @@
         assertThat(stoppedCallback2).isFalse();
     }
 
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testCreateProjection_keyguardLocked() throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+        doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
+                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+
+        assertThat(mService.getActiveProjectionInfo()).isNull();
+        assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testCreateProjection_keyguardLocked_packageAllowlisted()
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+        doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager)
+                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+
+        // The projection was started because it was allowed to capture the keyguard.
+        assertThat(mService.getActiveProjectionInfo()).isNotNull();
+    }
+
     @Test
     public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
             throws NameNotFoundException {
@@ -317,6 +361,48 @@
         assertThat(secondProjection).isNotEqualTo(projection);
     }
 
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testKeyguardLocked_stopsActiveProjection() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+
+        assertThat(service.getActiveProjectionInfo()).isNotNull();
+
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
+                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+        service.onKeyguardLockedStateChanged(true);
+
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN);
+        assertThat(service.getActiveProjectionInfo()).isNull();
+        assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection()
+            throws NameNotFoundException {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+
+        assertThat(service.getActiveProjectionInfo()).isNotNull();
+
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, projection.packageName);
+        service.onKeyguardLockedStateChanged(true);
+
+        verifyZeroInteractions(mMediaProjectionMetricsLogger);
+        assertThat(service.getActiveProjectionInfo()).isNotNull();
+    }
+
     @Test
     public void stop_noActiveProjections_doesNotLog() throws Exception {
         MediaProjectionManagerService service =
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 02d3b59..d714db99 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1060,6 +1060,23 @@
         assertThrows(SecurityException.class, userProps::getAlwaysVisible);
     }
 
+    /**
+     * Test that UserManager.getUserProperties throws the IllegalArgumentException for unsupported
+     * arguments such as UserHandle.NULL, UserHandle.CURRENT or UserHandle.ALL.
+     **/
+    @MediumTest
+    @Test
+    public void testThrowUserPropertiesForUnsupportedUserHandles() throws Exception {
+        assertThrows(IllegalArgumentException.class, () ->
+            mUserManager.getUserProperties(UserHandle.of(UserHandle.USER_NULL)));
+        assertThrows(IllegalArgumentException.class, () ->
+            mUserManager.getUserProperties(UserHandle.CURRENT));
+        assertThrows(IllegalArgumentException.class, () ->
+            mUserManager.getUserProperties(UserHandle.CURRENT_OR_SELF));
+        assertThrows(IllegalArgumentException.class, () ->
+            mUserManager.getUserProperties(UserHandle.ALL));
+    }
+
     // Make sure only max managed profiles can be created
     @MediumTest
     @Test
@@ -1845,6 +1862,25 @@
         assertThat(profilesExcludingHidden).asList().doesNotContain(profile.id);
     }
 
+    /**
+     * Test that UserManager.isQuietModeEnabled return false for unsupported
+     * arguments such as UserHandle.NULL, UserHandle.CURRENT or UserHandle.ALL.
+     **/
+    @MediumTest
+    @Test
+    public void testQuietModeEnabledForUnsupportedUserHandles() throws Exception {
+        assumeManagedUsersSupported();
+        final int mainUserId = mUserManager.getMainUser().getIdentifier();
+        UserInfo userInfo = createProfileForUser("Profile",
+                UserManager.USER_TYPE_PROFILE_MANAGED, mainUserId);
+        mUserManager.requestQuietModeEnabled(true, userInfo.getUserHandle());
+        assertThat(mUserManager.isQuietModeEnabled(userInfo.getUserHandle())).isTrue();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.of(UserHandle.USER_NULL))).isFalse();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.CURRENT)).isFalse();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.CURRENT_OR_SELF)).isFalse();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.ALL)).isFalse();
+    }
+
     private String generateLongString() {
         String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
                 + "Name Test Name Test Name Test Name "; //String of length 100
diff --git a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
index 698f094..6b32be0 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
@@ -16,9 +16,14 @@
 
 package com.android.server.power;
 
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
 import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
 
 import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN;
 import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_TOUCH;
@@ -27,6 +32,10 @@
 import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION;
 import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_SUCCESS;
 import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_BRIGHT_INITIATED_REVERT;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_BRIGHT_UNDIM;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_OFF_POWER_BUTTON;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_OFF_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -40,18 +49,24 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
 
 import org.junit.After;
 import org.junit.Before;
@@ -65,25 +80,18 @@
 public class WakefulnessSessionObserverTest {
     private static final int DEFAULT_SCREEN_OFF_TIMEOUT_MS = 30000;
     private static final int OVERRIDE_SCREEN_OFF_TIMEOUT_MS = 15000;
+    private static final int DISPLAY_PORT = 0xFF;
+    private static final long DISPLAY_MODEL = 0xEEEEEEEEL;
     private WakefulnessSessionObserver mWakefulnessSessionObserver;
     private Context mContext;
     private OffsettableClock mTestClock;
     @Mock
     private WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger
             mWakefulnessSessionFrameworkStatsLogger;
-    private WakefulnessSessionObserver.Injector mInjector =
-            new WakefulnessSessionObserver.Injector() {
-                @Override
-                WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger
-                        getWakefulnessSessionFrameworkStatsLogger() {
-                    return mWakefulnessSessionFrameworkStatsLogger;
-                }
-                @Override
-                WakefulnessSessionObserver.Clock getClock() {
-                    return mTestClock::now;
-                }
-            };
+    @Mock
+    private DisplayManagerInternal mDisplayManagerInternal;
 
+    private TestHandler mHandler;
     @Before
     public void setUp() {
         mTestClock = new OffsettableClock.Stopped();
@@ -95,7 +103,7 @@
 
         final Resources res = spy(mContext.getResources());
         doReturn(OVERRIDE_SCREEN_OFF_TIMEOUT_MS).when(res).getInteger(
-                com.android.internal.R.integer.config_screenTimeoutOverride);
+                R.integer.config_screenTimeoutOverride);
         when(mContext.getResources()).thenReturn(res);
         FakeSettingsProvider.clearSettingsProvider();
         MockContentResolver mockContentResolver = new MockContentResolver();
@@ -104,7 +112,32 @@
         Settings.System.putIntForUser(mockContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
                 DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT);
 
-        mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, mInjector);
+        final DisplayInfo info = new DisplayInfo();
+        info.address = DisplayAddress.fromPortAndModel(DISPLAY_PORT, DISPLAY_MODEL);
+        mHandler = new TestHandler(null);
+        mWakefulnessSessionObserver = new WakefulnessSessionObserver(
+                mContext, new WakefulnessSessionObserver.Injector() {
+                    @Override
+                    WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger
+                            getWakefulnessSessionFrameworkStatsLogger() {
+                        return mWakefulnessSessionFrameworkStatsLogger;
+                    }
+                    @Override
+                    WakefulnessSessionObserver.Clock getClock() {
+                        return mTestClock::now;
+                    }
+                    @Override
+                    Handler getHandler() {
+                        return mHandler;
+                    }
+                    @Override
+                    DisplayManagerInternal getDisplayManagerInternal() {
+                        when(mDisplayManagerInternal.getDisplayInfo(DEFAULT_DISPLAY))
+                                .thenReturn(info);
+                        return mDisplayManagerInternal;
+                    }
+                }
+        );
     }
 
     @After
@@ -317,6 +350,167 @@
                         DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default timeout ms
     }
 
+    @Test
+    public void testOnScreenPolicyUpdate_OffByTimeout() {
+        int userActivity = PowerManager.USER_ACTIVITY_EVENT_ATTENTION;
+        long userActivityTimestamp = mTestClock.now();
+        mWakefulnessSessionObserver.notifyUserActivity(
+                userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+        mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+                DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_AWAKE,
+                WAKE_REASON_POWER_BUTTON, mTestClock.now());
+        int advancedTime = 5;
+        advanceTime(advancedTime);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(mTestClock.now(), DEFAULT_DISPLAY_GROUP,
+                POLICY_OFF);
+        mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+                DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_ASLEEP,
+                GO_TO_SLEEP_REASON_TIMEOUT, mTestClock.now());
+
+        verify(mWakefulnessSessionFrameworkStatsLogger)
+                .logDimEvent(
+                        DISPLAY_PORT, // physical display port id
+                        POLICY_REASON_OFF_TIMEOUT, // policy reason
+                        userActivity, // last user activity event
+                        advancedTime, // last user activity timestamp
+                        advancedTime, // dim duration ms
+                        DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+    }
+
+    @Test
+    public void testOnScreenPolicyUpdate_NoLogging_NotDefaultDisplayGroup() {
+        int powerGroupId = 1;
+        int userActivity = PowerManager.USER_ACTIVITY_EVENT_ATTENTION;
+        long userActivityTimestamp = mTestClock.now();
+        int advancedTime = 5;
+        mWakefulnessSessionObserver.notifyUserActivity(
+                userActivityTimestamp, powerGroupId, userActivity);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), powerGroupId, POLICY_DIM);
+        advanceTime(advancedTime);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(mTestClock.now(), powerGroupId,
+                POLICY_OFF);
+
+        verify(mWakefulnessSessionFrameworkStatsLogger, never())
+                .logDimEvent(
+                        DISPLAY_PORT, // physical display port id
+                        POLICY_REASON_OFF_TIMEOUT, // policy reason
+                        userActivity, // last user activity event
+                        advancedTime, // last user activity timestamp
+                        advancedTime, // dim duration ms
+                        DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+    }
+
+    @Test
+    public void testOnScreenPolicyUpdate_OffByPowerButton() {
+        // ----- initialize start -----
+        mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+                DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_AWAKE,
+                WAKE_REASON_POWER_BUTTON, mTestClock.now());
+
+        int userActivity = PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY;
+        long userActivityTimestamp = mTestClock.now();
+        mWakefulnessSessionObserver.notifyUserActivity(
+                userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+        // ----- initialize end -----
+
+        int dimDuration = 500;
+        advanceTime(dimDuration);
+        int userActivityDuration = dimDuration;
+        mWakefulnessSessionObserver.notifyUserActivity(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, PowerManager.USER_ACTIVITY_EVENT_BUTTON);
+        mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+                DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_ASLEEP,
+                GO_TO_SLEEP_REASON_POWER_BUTTON, mTestClock.now());
+
+        verify(mWakefulnessSessionFrameworkStatsLogger)
+                .logDimEvent(
+                        DISPLAY_PORT, // physical display port id
+                        POLICY_REASON_OFF_POWER_BUTTON, // policy reason
+                        userActivity, // last user activity event
+                        userActivityDuration, // last user activity timestamp
+                        dimDuration, // dim duration ms
+                        DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+        assertThat(mHandler.getPendingMessages()).isEmpty();
+    }
+
+    @Test
+    public void testOnScreenPolicyUpdate_Undim() {
+        // ----- initialize start -----
+        int userActivity = PowerManager.USER_ACTIVITY_EVENT_TOUCH;
+        long userActivityTimestamp = mTestClock.now();
+        mWakefulnessSessionObserver.notifyUserActivity(
+                userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+        mWakefulnessSessionObserver.mPowerGroups.get(DEFAULT_DISPLAY_GROUP).mIsInteractive = true;
+        // ----- initialize end -----
+
+        int dimDurationMs = 5;
+        advanceTime(dimDurationMs);
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT);
+
+        int expectedLastUserActivityTimeMs = (int) (mTestClock.now() - userActivityTimestamp);
+
+        mHandler.flush();
+        verify(mWakefulnessSessionFrameworkStatsLogger)
+                .logDimEvent(
+                        DISPLAY_PORT, // physical display port id
+                        POLICY_REASON_BRIGHT_UNDIM, // policy reason
+                        userActivity, // last user activity event
+                        expectedLastUserActivityTimeMs, // last user activity timestamp
+                        dimDurationMs, // dim duration ms
+                        DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+    }
+
+    @Test
+    public void testOnScreenPolicyUpdate_BrightInitiatedRevert() {
+        // ----- initialize start -----
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+        int dimDurationMs = 500;
+        advanceTime(dimDurationMs);
+        int userActivity = PowerManager.USER_ACTIVITY_EVENT_BUTTON;
+        long userActivityTimestamp = mTestClock.now();
+        mWakefulnessSessionObserver.notifyUserActivity(
+                userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+        int userActivityTime = 5;
+        advanceTime(userActivityTime);
+        dimDurationMs += userActivityTime;
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(
+                mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_OFF);
+        mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+                DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_ASLEEP,
+                GO_TO_SLEEP_REASON_POWER_BUTTON, mTestClock.now());
+
+        mWakefulnessSessionObserver.mPowerGroups.get(DEFAULT_DISPLAY_GROUP)
+                .mPastDimDurationMs = dimDurationMs;
+        // ----- initialize end -----
+
+        int advancedTime = 5;
+        advanceTime(advancedTime); // shorter than 5000 ms
+        userActivityTime += advancedTime;
+        mWakefulnessSessionObserver.onScreenPolicyUpdate(mTestClock.now(), DEFAULT_DISPLAY_GROUP,
+                POLICY_BRIGHT);
+        mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+                DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_AWAKE,
+                WAKE_REASON_POWER_BUTTON, mTestClock.now());
+
+        verify(mWakefulnessSessionFrameworkStatsLogger)
+                .logDimEvent(
+                        DISPLAY_PORT, // physical display port id
+                        POLICY_REASON_BRIGHT_INITIATED_REVERT, // policy reason
+                        userActivity, // last user activity event
+                        userActivityTime, // last user activity timestamp
+                        dimDurationMs, // dim duration ms
+                        DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+    }
+
     private void advanceTime(long timeMs) {
         mTestClock.fastForward(timeMs);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index eb8825c..b4505fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -684,7 +684,8 @@
 
         // Asserts fixed orientation request is not ignored, and the orientation is changed.
         assertNotEquals(activityCurOrientation, activity.getConfiguration().orientation);
-        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
     @Test
@@ -717,7 +718,8 @@
 
         // Relaunching the app should still respect the orientation request.
         assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
-        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index f79cdc1..867f01f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -143,8 +143,8 @@
     }
 
     void setLetterboxedForFixedOrientationAndAspectRatio(boolean enabled) {
-        doReturn(enabled).when(mActivityStack.top())
-                .isLetterboxedForFixedOrientationAndAspectRatio();
+        doReturn(enabled).when(mActivityStack.top().mAppCompatController
+                .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
     }
 
     void enableTreatmentForTopActivity(boolean enabled) {
@@ -163,8 +163,8 @@
     }
 
     void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
-        doReturn(enabled).when(mActivityStack.top()
-                .mLetterboxUiController).shouldApplyUserMinAspectRatioOverride();
+        doReturn(enabled).when(mActivityStack.top().mAppCompatController
+                .getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
     }
 
     void setShouldCreateCompatDisplayInsets(boolean enabled) {
@@ -172,13 +172,14 @@
     }
 
     void setShouldApplyUserFullscreenOverride(boolean enabled) {
-        doReturn(enabled).when(mActivityStack.top()
-                .mLetterboxUiController).shouldApplyUserFullscreenOverride();
+        doReturn(enabled).when(mActivityStack.top().mAppCompatController
+                .getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
     }
 
     void setGetUserMinAspectRatioOverrideCode(@PackageManager.UserMinAspectRatio int orientation) {
         doReturn(orientation).when(mActivityStack.top()
-                .mLetterboxUiController).getUserMinAspectRatioOverrideCode();
+                .mAppCompatController.getAppCompatAspectRatioOverrides())
+                .getUserMinAspectRatioOverrideCode();
     }
 
     void setIgnoreOrientationRequest(boolean enabled) {
@@ -401,6 +402,8 @@
         mActivityStack.push(activity);
         spyOn(activity);
         spyOn(activity.mAppCompatController.getTransparentPolicy());
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
         spyOn(activity.mLetterboxUiController);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index 83ad7b1..708d686 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -214,7 +214,8 @@
         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
 
         // DAG is portrait (860x1200), so Task and Activity fill DAG.
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
         assertThat(taskBounds).isEqualTo(dagBounds);
         assertThat(activityBounds).isEqualTo(taskBounds);
@@ -238,7 +239,8 @@
                 new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());
 
         // DAG is landscape (1200x860), no fixed orientation letterbox
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
         assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -262,11 +264,13 @@
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
 
         rotateDisplay(mDisplay, ROTATION_90);
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
     }
 
@@ -283,7 +287,8 @@
 
         // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
         // (860x[860x860/1200=616]). Task fills DAG.
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
         assertThat(taskBounds).isEqualTo(dagBounds);
         assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
@@ -300,7 +305,8 @@
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
     }
 
     @Test
@@ -310,7 +316,8 @@
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED);
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
     }
 
     @Test
@@ -329,7 +336,8 @@
         final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());
 
         // DAG is landscape (1200x860), no fixed orientation letterbox
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
         assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -354,7 +362,8 @@
 
         rotateDisplay(mDisplay, ROTATION_90);
 
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
     }
 
@@ -522,7 +531,8 @@
         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
 
         // Launch portrait on second DAG
@@ -534,13 +544,15 @@
         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
         assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
         assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
-        assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+        assertThat(mSecondActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mSecondActivity.inSizeCompatMode()).isFalse();
 
         // First activity is letterboxed in portrait as requested.
         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
-        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
+        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
 
     }
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 51b3c48..c42367e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -293,6 +293,7 @@
         mainWindow.mInvGlobalScale = 1f;
         spyOn(resources);
         spyOn(mActivity);
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy());
 
         if (taskbar != null) {
             taskbar.setVisible(true);
@@ -301,7 +302,8 @@
         doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
         doReturn(false).when(mActivity).isInLetterboxAnimation();
         doReturn(true).when(mActivity).isVisible();
-        doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+        doReturn(true).when(mActivity.mAppCompatController
+                .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
         doReturn(insets).when(mainWindow).getInsetsState();
         doReturn(attrs).when(mainWindow).getAttrs();
         doReturn(true).when(mainWindow).isDrawn();
@@ -324,10 +326,11 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
                 /* value */ true);
 
-        mController = new LetterboxUiController(mWm, mActivity);
         doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldApplyUserFullscreenOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserFullscreenOverride());
     }
 
     @Test
@@ -337,9 +340,10 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
                 /* value */ false);
 
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldApplyUserFullscreenOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserFullscreenOverride());
     }
 
     @Test
@@ -348,16 +352,18 @@
         prepareActivityThatShouldApplyUserFullscreenOverride();
         mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
 
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldApplyUserFullscreenOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserFullscreenOverride());
     }
 
     @Test
     public void testShouldApplyUserFullscreenOverride_returnsTrue() {
         prepareActivityThatShouldApplyUserFullscreenOverride();
 
-        assertTrue(mController.shouldApplyUserFullscreenOverride());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserFullscreenOverride());
     }
 
     @Test
@@ -369,18 +375,22 @@
         mActivity = setUpActivityWithComponent();
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldEnableUserAspectRatioSettings());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldEnableUserAspectRatioSettings());
     }
 
     @Test
     public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue()
             throws Exception {
-        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
         mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+        mActivity = setUpActivityWithComponent();
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
 
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertTrue(mController.shouldEnableUserAspectRatioSettings());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldEnableUserAspectRatioSettings());
     }
 
     @Test
@@ -391,7 +401,8 @@
 
         mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldEnableUserAspectRatioSettings());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldEnableUserAspectRatioSettings());
     }
 
     @Test
@@ -400,9 +411,10 @@
         prepareActivityThatShouldApplyUserMinAspectRatioOverride();
         mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
 
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldEnableUserAspectRatioSettings());
     }
 
     @Test
@@ -411,9 +423,10 @@
         doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
         mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
 
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserMinAspectRatioOverride());
     }
 
     @Test
@@ -421,30 +434,35 @@
         prepareActivityThatShouldApplyUserMinAspectRatioOverride();
         mDisplayContent.setIgnoreOrientationRequest(false);
 
-        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserMinAspectRatioOverride());
     }
 
     @Test
     public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
         prepareActivityThatShouldApplyUserMinAspectRatioOverride();
 
-        assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserMinAspectRatioOverride());
     }
 
     @Test
     public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientation_returnsFalse() {
         prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
 
-        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldApplyUserMinAspectRatioOverride());
     }
 
     private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
             boolean orientationRequest) {
-        spyOn(mController);
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(orientationRequest).when(
                 mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
         mDisplayContent.setIgnoreOrientationRequest(true);
-        doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+        doReturn(USER_MIN_ASPECT_RATIO_3_2)
+                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .getUserMinAspectRatioOverrideCode();
     }
 
     private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
@@ -452,10 +470,11 @@
     }
 
     private void prepareActivityThatShouldApplyUserFullscreenOverride() {
-        spyOn(mController);
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
         mDisplayContent.setIgnoreOrientationRequest(true);
-        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .getUserMinAspectRatioOverrideCode();
     }
 
@@ -578,9 +597,10 @@
     @Test
     @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
     public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertTrue(mController.shouldOverrideMinAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio());
     }
 
     @Test
@@ -588,9 +608,10 @@
     public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue()
             throws Exception {
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertTrue(mController.shouldOverrideMinAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio());
     }
 
     @Test
@@ -598,17 +619,19 @@
     public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse()
             throws Exception {
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldOverrideMinAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio());
     }
 
     @Test
     @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
     public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldOverrideMinAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio());
     }
 
     @Test
@@ -618,9 +641,9 @@
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
 
         mActivity = setUpActivityWithComponent();
-        mController = new LetterboxUiController(mWm, mActivity);
 
-        assertFalse(mController.shouldOverrideMinAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio());
     }
 
     @Test
@@ -628,9 +651,10 @@
     public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse()
             throws Exception {
         mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
-        mController = new LetterboxUiController(mWm, mActivity);
+        mActivity = setUpActivityWithComponent();
 
-        assertFalse(mController.shouldOverrideMinAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .shouldOverrideMinAspectRatio());
     }
 
     @Test
@@ -860,8 +884,10 @@
         doReturn(true).when(mDisplayContent.mAppCompatCameraPolicy)
                 .isTreatmentEnabledForActivity(eq(mActivity));
 
-        assertEquals(mController.getSplitScreenAspectRatio(),
-                mController.getFixedOrientationLetterboxAspectRatio(
+        final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+        assertEquals(aspectRatioOverrides.getSplitScreenAspectRatio(),
+                aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(
                         mActivity.getParent().getConfiguration()), /* delta */  0.01);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 3078df0..d6d8339 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -428,7 +428,8 @@
         final Rect bounds = new Rect(task.getBounds());
         bounds.scale(0.5f);
         task.setBounds(bounds);
-        assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(task.autoRemoveRecents).isFalse();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 64527cb..ae88b1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -64,6 +64,7 @@
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -103,6 +104,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -494,7 +496,7 @@
         // Activity is sandboxed; it is in size compat mode since it is not resizable and has a
         // max aspect ratio.
         assertActivityMaxBoundsSandboxed();
-        assertScaled();
+        assertDownScaled();
     }
 
     @Test
@@ -514,7 +516,7 @@
         // The bounds should be [100, 0 - 1100, 2500].
         assertEquals(origBounds.width(), currentBounds.width());
         assertEquals(origBounds.height(), currentBounds.height());
-        assertScaled();
+        assertDownScaled();
 
         // The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100.
         final float scale = (float) display.mBaseDisplayHeight / currentBounds.height();
@@ -554,7 +556,7 @@
         assertEquals(origBounds.width(), currentBounds.width());
         assertEquals(origBounds.height(), currentBounds.height());
         assertEquals(offsetX, mActivity.getBounds().left);
-        assertScaled();
+        assertDownScaled();
         // Activity is sandboxed due to size compat mode.
         assertActivityMaxBoundsSandboxed();
 
@@ -692,7 +694,7 @@
         // The configuration bounds [820, 0 - 1820, 2500] should keep the same.
         assertEquals(originalBounds.width(), currentBounds.width());
         assertEquals(originalBounds.height(), currentBounds.height());
-        assertScaled();
+        assertDownScaled();
         // Activity max bounds are sandboxed due to size compat mode on the new display.
         assertActivityMaxBoundsSandboxed();
 
@@ -751,7 +753,7 @@
         assertEquals(origAppBounds.width(), appBounds.width());
         assertEquals(origAppBounds.height(), appBounds.height());
         // The activity is 1000x1400 and the display is 2500x1000.
-        assertScaled();
+        assertDownScaled();
         final float scale = mActivity.getCompatScale();
         // The position in configuration should be in app coordinates.
         final Rect screenBounds = mActivity.getBounds();
@@ -848,7 +850,7 @@
         // Size compatibility mode is able to handle orientation change so the process shouldn't be
         // restarted and the override configuration won't be cleared.
         verify(mActivity, never()).restartProcessIfVisible();
-        assertScaled();
+        assertDownScaled();
         // Activity max bounds are sandboxed due to size compat mode, even if is not visible.
         assertActivityMaxBoundsSandboxed();
 
@@ -1081,10 +1083,11 @@
 
         // Simulate the user selecting the fullscreen user aspect ratio override
         spyOn(activity.mWmService.mLetterboxConfiguration);
-        spyOn(activity.mLetterboxUiController);
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
                 .isUserAppAspectRatioFullscreenEnabled();
-        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(activity.mLetterboxUiController)
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+                .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .getUserMinAspectRatioOverrideCode();
         assertFalse(activity.shouldCreateCompatDisplayInsets());
     }
@@ -1102,9 +1105,10 @@
                 RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
 
         // Simulate the user selecting the fullscreen user aspect ratio override
-        spyOn(activity.mLetterboxUiController);
-        doReturn(true).when(activity.mLetterboxUiController)
-                .isSystemOverrideToFullscreenEnabled();
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .isSystemOverrideToFullscreenEnabled();
         assertFalse(activity.shouldCreateCompatDisplayInsets());
     }
 
@@ -1123,7 +1127,8 @@
         prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
 
         // Activity max bounds should not be sandboxed, even though it is letterboxed.
-        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                 .isEqualTo(activity.getDisplayArea().getBounds());
     }
@@ -1164,7 +1169,8 @@
         prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
 
         // Activity max bounds should not be sandboxed, even though it is letterboxed.
-        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                 .isEqualTo(activity.getDisplayArea().getBounds());
     }
@@ -1187,7 +1193,8 @@
         prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
 
         // Activity max bounds should not be sandboxed, even though it is letterboxed.
-        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                 .isEqualTo(activity.getDisplayArea().getBounds());
     }
@@ -1618,6 +1625,85 @@
                 activity.getBounds().width(), 0.5);
     }
 
+
+    /**
+     * Test that a freeform unresizeable activity can be down-scaled to fill its smaller parent
+     * bounds.
+     */
+    @Test
+    public void testCompatScaling_freeformUnresizeableApp_largerThanParent_downScaled() {
+        final int dw = 600;
+        final int dh = 800;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(mActivity.inSizeCompatMode());
+
+        // Resize app to make original app bounds larger than parent bounds.
+        mTask.getWindowConfiguration().setAppBounds(
+                new Rect(0, 0, dw - 300, dh - 400));
+        mActivity.onConfigurationChanged(mTask.getConfiguration());
+        // App should enter size compat mode and be down-scaled to fill new parent bounds.
+        assertDownScaled();
+    }
+
+    /**
+     * Test that when desktop mode is enabled, a freeform unresizeable activity can be up-scaled to
+     * fill its larger parent bounds.
+     */
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void testCompatScaling_freeformUnresizeableApp_smallerThanParent_upScaled() {
+        doReturn(true).when(() ->
+                DesktopModeLaunchParamsModifier.canEnterDesktopMode(any()));
+        final int dw = 600;
+        final int dh = 800;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(mActivity.inSizeCompatMode());
+
+        // Resize app to make original app bounds smaller than parent bounds.
+        mTask.getWindowConfiguration().setAppBounds(
+                new Rect(0, 0, dw + 300, dh + 400));
+        mActivity.onConfigurationChanged(mTask.getConfiguration());
+        // App should enter size compat mode and be up-scaled to fill parent bounds.
+        assertUpScaled();
+    }
+
+    /**
+     * Test that when desktop mode is disabled, a freeform unresizeable activity cannot be up-scaled
+     * despite its larger parent bounds.
+     */
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void testSizeCompatScaling_freeformUnresizeableApp_smallerThanParent_notScaled() {
+        final int dw = 600;
+        final int dh = 800;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(mActivity.inSizeCompatMode());
+        final Rect originalAppBounds = mActivity.getBounds();
+
+        // Resize app to make original app bounds smaller than parent bounds.
+        mTask.getWindowConfiguration().setAppBounds(
+                new Rect(0, 0, dw + 300, dh + 400));
+        mActivity.onConfigurationChanged(mTask.getConfiguration());
+        // App should enter size compat mode but remain its original size.
+        assertTrue(mActivity.inSizeCompatMode());
+        assertEquals(originalAppBounds, mActivity.getBounds());
+    }
+
     @Test
     public void testGetLetterboxInnerBounds_noScalingApplied() {
         // Set up a display in portrait and ignoring orientation request.
@@ -1636,7 +1722,8 @@
         addWindowToActivity(mActivity);
 
         // App should launch in fullscreen.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Activity inherits max bounds from TaskDisplayArea.
@@ -1650,8 +1737,9 @@
         assertTrue(rotatedDisplayBounds.width() < rotatedDisplayBounds.height());
 
         // App should be in size compat.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
@@ -1762,7 +1850,8 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
 
@@ -1793,7 +1882,8 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Activity bounds should respect minimum aspect ratio for activity.
@@ -1823,7 +1913,8 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Activity bounds should respect maximum aspect ratio for activity.
@@ -1853,7 +1944,8 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Activity bounds should respect aspect ratio override for fixed orientation letterbox.
@@ -1950,7 +2042,8 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Letterbox logic should use config_letterboxDefaultMinAspectRatioForUnresizableApps over
@@ -1976,7 +2069,8 @@
 
         // App should launch in fixed orientation letterbox.
         // Activity bounds should be 700x1400 with the ratio as the display.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFitted();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
         assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
@@ -1986,7 +2080,7 @@
 
         // After we rotate, the activity should go in the size-compat mode and report the same
         // configuration values.
-        assertScaled();
+        assertDownScaled();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
         assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
@@ -2018,7 +2112,8 @@
 
         // App should launch in fixed orientation letterbox.
         // Activity bounds should be 700x1400 with the ratio as the display.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFitted();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
         assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
@@ -2052,7 +2147,8 @@
         final Rect activityBounds = new Rect(mActivity.getBounds());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
 
@@ -2081,9 +2177,10 @@
                 .isUserAppAspectRatioFullscreenEnabled();
 
         // Set user aspect ratio override
-        spyOn(mActivity.mLetterboxUiController);
-        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
-                .getUserMinAspectRatioOverrideCode();
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .getUserMinAspectRatioOverrideCode();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -2105,9 +2202,10 @@
                 .isUserAppAspectRatioFullscreenEnabled();
 
         // Set user aspect ratio override
-        spyOn(mActivity.mLetterboxUiController);
-        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
-                .getUserMinAspectRatioOverrideCode();
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .getUserMinAspectRatioOverrideCode();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
 
@@ -2124,9 +2222,10 @@
         final int displayHeight = 1400;
         setUpDisplaySizeWithApp(displayWidth, displayHeight);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        spyOn(mActivity.mLetterboxUiController);
-        doReturn(true).when(mActivity.mLetterboxUiController)
-                .isSystemOverrideToFullscreenEnabled();
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .isSystemOverrideToFullscreenEnabled();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -2143,9 +2242,10 @@
         final int displayHeight = 1600;
         setUpDisplaySizeWithApp(displayWidth, displayHeight);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        spyOn(mActivity.mLetterboxUiController);
-        doReturn(true).when(mActivity.mLetterboxUiController)
-                .isSystemOverrideToFullscreenEnabled();
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .isSystemOverrideToFullscreenEnabled();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
 
@@ -2588,7 +2688,8 @@
         final Rect activityBounds = new Rect(mActivity.getBounds());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
 
@@ -2632,13 +2733,14 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
         // Check that the display aspect ratio is used by the app.
         final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+                DELTA_ASPECT_RATIO_TOLERANCE);
     }
 
     @Test
@@ -2667,13 +2769,14 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
         // Check that the display aspect ratio is used by the app.
         final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+                DELTA_ASPECT_RATIO_TOLERANCE);
     }
 
     @Test
@@ -2693,13 +2796,14 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
         // Check that the display aspect ratio is used by the app.
         final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+                DELTA_ASPECT_RATIO_TOLERANCE);
     }
 
     @Test
@@ -2719,13 +2823,14 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
         // Check that the display aspect ratio is used by the app.
         final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+                DELTA_ASPECT_RATIO_TOLERANCE);
     }
 
     @Test
@@ -2748,8 +2853,9 @@
         assertTrue(displayBounds.width() < displayBounds.height());
 
         // App should be in size compat.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertEquals(activityBounds.width(), newActivityBounds.width());
         assertEquals(activityBounds.height(), newActivityBounds.height());
         assertActivityMaxBoundsSandboxed();
@@ -2765,7 +2871,8 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
         // App should launch in fullscreen.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         // Activity inherits max bounds from TaskDisplayArea.
         assertMaxBoundsInheritDisplayAreaBounds();
@@ -2778,8 +2885,9 @@
         assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height());
 
         // App should be in size compat.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
@@ -2799,7 +2907,8 @@
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Launch another portrait fixed app.
@@ -2821,7 +2930,8 @@
 
         // Task and display bounds should be equal while activity should be letterboxed and
         // has 700x1400 bounds with the ratio as the display.
-        assertTrue(newActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(newActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(newActivity.inSizeCompatMode());
         // Activity max bounds are sandboxed due to size compat mode.
         assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds())
@@ -2842,7 +2952,8 @@
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         mActivity.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
@@ -2863,7 +2974,8 @@
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Launch another portrait fixed app with max aspect ratio as 1.3.
@@ -2893,7 +3005,8 @@
         assertActivityMaxBoundsSandboxed();
 
         // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(newActivity.inSizeCompatMode());
         assertEquals(displayBounds.height(), newActivityBounds.height());
         assertEquals((long) Math.rint(newActivityBounds.height()
@@ -2912,15 +3025,17 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
         clearInvocations(mActivity);
 
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
         // Rotate display to portrait.
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
         // App should be in size compat.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         // Activity max bounds are sandboxed due to size compat mode.
         assertActivityMaxBoundsSandboxed();
@@ -2930,8 +3045,9 @@
 
         // App still in size compat, and the bounds don't change.
         verify(mActivity, never()).clearSizeCompatMode();
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertEquals(activityBounds, mActivity.getBounds());
         // Activity max bounds are sandboxed due to size compat.
         assertActivityMaxBoundsSandboxed();
@@ -2948,7 +3064,8 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
         // In fixed orientation letterbox
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
 
@@ -2956,15 +3073,17 @@
         rotateDisplay(display, ROTATION_90);
 
         // App should be in size compat.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to landscape.
         rotateDisplay(display, ROTATION_180);
 
         // In activity letterbox
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
     }
@@ -2982,7 +3101,8 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);
 
         // In fixed orientation letterbox
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
 
@@ -2990,15 +3110,17 @@
         rotateDisplay(display, ROTATION_90);
 
         // App should be in size compat.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertDownScaled();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to portrait.
         rotateDisplay(display, ROTATION_180);
 
         // In fixed orientation letterbox
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
     }
@@ -3182,7 +3304,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // Non-resizable activity in size compat mode
-        assertScaled();
+        assertDownScaled();
         final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
         assertEquals(originalBounds.width(), newBounds.width());
         assertEquals(originalBounds.height(), newBounds.height());
@@ -3195,7 +3317,8 @@
         assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
         assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
         assertFitted();
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertActivityMaxBoundsSandboxed();
 
         // Letterbox should fill the gap between the split screen and the letterboxed activity.
@@ -3221,7 +3344,8 @@
 
         // Resizable activity is not in size compat mode but in the letterbox for fixed orientation.
         assertFitted();
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
     @Test
@@ -3244,7 +3368,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // Non-resizable activity in size compat mode
-        assertScaled();
+        assertDownScaled();
         final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
         assertEquals(originalBounds.width(), newBounds.width());
         assertEquals(originalBounds.height(), newBounds.height());
@@ -3257,7 +3381,8 @@
         assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
         assertEquals(ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
         assertFitted();
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertActivityMaxBoundsSandboxed();
 
         // Activity bounds fill split screen.
@@ -3284,7 +3409,7 @@
         organizer.mPrimary.setBounds(0, 0, 1000, 800);
 
         // Non-resizable activity should be in size compat mode.
-        assertScaled();
+        assertDownScaled();
         assertEquals(mActivity.getBounds(), new Rect(60, 0, 940, 800));
 
         recomputeNaturalConfigurationOfUnresizableActivity();
@@ -3861,7 +3986,7 @@
         // Force activity to scaled down for size compat mode.
         resizeDisplay(mTask.mDisplayContent, 700, 1400);
         assertTrue(mActivity.inSizeCompatMode());
-        assertScaled();
+        assertDownScaled();
         assertEquals(sizeCompatScaled, mActivity.getBounds());
     }
 
@@ -3892,7 +4017,8 @@
         // orientation is not respected with insets as insets have been decoupled.
         final Rect appBounds = activity.getWindowConfiguration().getAppBounds();
         final Rect displayBounds = display.getBounds();
-        assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertNotNull(appBounds);
         assertEquals(displayBounds.width(), appBounds.width());
         assertEquals(displayBounds.height(), appBounds.height());
@@ -3923,7 +4049,8 @@
 
         final Rect bounds = activity.getBounds();
         // Activity should be letterboxed and should have portrait app bounds
-        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(bounds.height() > bounds.width());
     }
 
@@ -3957,7 +4084,8 @@
         assertNotNull(activity.getCompatDisplayInsets());
         // Activity is not letterboxed for fixed orientation because orientation is respected
         // with insets, and should not be in size compat mode
-        assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(activity.inSizeCompatMode());
     }
 
@@ -4204,9 +4332,10 @@
 
         Configuration parentConfig = mActivity.getParent().getConfiguration();
 
-        float actual = mActivity.mLetterboxUiController
-                .getFixedOrientationLetterboxAspectRatio(parentConfig);
-        float expected = mActivity.mLetterboxUiController.getSplitScreenAspectRatio();
+        final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+        float actual = aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(parentConfig);
+        float expected = aspectRatioOverrides.getSplitScreenAspectRatio();
 
         assertEquals(expected, actual, DELTA_ASPECT_RATIO_TOLERANCE);
     }
@@ -4357,7 +4486,7 @@
         resizeDisplay(mTask.mDisplayContent, 1400, 700);
 
         assertTrue(mActivity.inSizeCompatMode());
-        assertScaled();
+        assertDownScaled();
         assertEquals(sizeCompatScaled, mActivity.getBounds());
     }
 
@@ -4380,7 +4509,8 @@
         verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
 
         prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
 
@@ -4396,7 +4526,8 @@
         // ActivityRecord#resolveSizeCompatModeConfiguration because mCompatDisplayInsets aren't
         // null but activity doesn't enter size compat mode. Checking that areBoundsLetterboxed()
         // still returns true because of the aspect ratio restrictions.
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
         verifyLogAppCompatState(mActivity,
@@ -4423,7 +4554,8 @@
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
         verifyLogAppCompatState(mActivity,
@@ -4441,7 +4573,8 @@
 
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
         verifyLogAppCompatState(mActivity,
@@ -4502,7 +4635,8 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
         assertFalse(mActivity.isEligibleForLetterboxEducation());
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
     @Test
@@ -4560,7 +4694,8 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
         assertTrue(mActivity.isEligibleForLetterboxEducation());
-        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
     @Test
@@ -4574,7 +4709,8 @@
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
         assertTrue(mActivity.isEligibleForLetterboxEducation());
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(mActivity.inSizeCompatMode());
     }
 
@@ -4616,7 +4752,7 @@
         // Target min aspect ratio must be larger than parent aspect ratio to be applied.
         final float targetMinAspectRatio = 3.0f;
 
-        // Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
+        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
         final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
                 .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .setMinAspectRatio(targetMinAspectRatio).build();
@@ -4630,7 +4766,7 @@
         final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
                 .windowConfiguration.getAppBounds());
 
-        // Create unresizeable fixed portait activity with min aspect ratio greater than parent
+        // Create unresizeable fixed portrait activity with min aspect ratio greater than parent
         // aspect ratio.
         final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
                 .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
@@ -4642,12 +4778,12 @@
                 .windowConfiguration.getAppBounds());
 
         // Check that aspect ratio of app bounds is equal to the min aspect ratio.
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
-        assertEquals(targetMinAspectRatio, ActivityRecord
-                .computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio,
+                computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio,
+                computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
+        assertEquals(targetMinAspectRatio,
+                computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
     }
 
     @Test
@@ -4663,7 +4799,7 @@
         // Activity should enter size compat with old density after display density change.
         display.setForcedDensity(newDensity, UserHandle.USER_CURRENT);
 
-        assertScaled();
+        assertDownScaled();
         assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
 
         // Activity should exit size compat with new density.
@@ -4902,14 +5038,25 @@
         }
     }
 
-    private void assertScaled() {
-        assertScaled(mActivity);
+    private void assertUpScaled() {
+        assertScaled(mActivity, /* upScalingExpected */ true);
     }
 
-    /** Asserts that the size of activity is larger than its parent so it is scaling. */
-    private void assertScaled(ActivityRecord activity) {
+    private void assertDownScaled() {
+        assertScaled(mActivity, /* upScalingExpected */ false);
+    }
+
+    /**
+     * Asserts that the size of an activity differs from its parent and so it is scaling (either up
+     * or down).
+     */
+    private void assertScaled(ActivityRecord activity, boolean upScalingExpected) {
         assertTrue(activity.inSizeCompatMode());
-        assertNotEquals(1f, activity.getCompatScale(), 0.0001f /* delta */);
+        if (upScalingExpected) {
+            assertTrue(activity.getCompatScale() > 1f);
+        } else {
+            assertTrue(activity.getCompatScale() < 1f);
+        }
     }
 
     private void assertFitted() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 35b6b70..47d34a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -759,20 +759,24 @@
         // Assert fixed orientation request is ignored for activity in ActivityEmbedding split.
         activity0.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
-        assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity0.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
 
         activity1.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity1.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
 
         // Also verify the behavior on device that ignore orientation request.
         mDisplayContent.setIgnoreOrientationRequest(true);
         task.onConfigurationChanged(task.getParent().getConfiguration());
 
-        assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
-        assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity0.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity1.mAppCompatController.getAppCompatAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
 
         tf0.setResumedActivity(activity0, "test");
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 4bc87b1..76690ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -613,11 +613,12 @@
         final ActivityRecord root = task.getTopNonFinishingActivity();
         spyOn(mWm.mLetterboxConfiguration);
         spyOn(root);
-        spyOn(root.mLetterboxUiController);
+        spyOn(root.mAppCompatController.getAppCompatAspectRatioOverrides());
 
         doReturn(true).when(root).fillsParent();
-        doReturn(true).when(root.mLetterboxUiController)
-                .shouldEnableUserAspectRatioSettings();
+        doReturn(true).when(
+                root.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .shouldEnableUserAspectRatioSettings();
         doReturn(false).when(root).inSizeCompatMode();
         doReturn(task).when(root).getOrganizedTask();
 
@@ -626,12 +627,13 @@
                 .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
 
         // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
-        doReturn(false).when(root.mLetterboxUiController)
-                .shouldEnableUserAspectRatioSettings();
+        doReturn(false).when(
+                root.mAppCompatController.getAppCompatAspectRatioOverrides())
+                    .shouldEnableUserAspectRatioSettings();
         assertFalse(task.getTaskInfo()
                 .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
-        doReturn(true).when(root.mLetterboxUiController)
-                .shouldEnableUserAspectRatioSettings();
+        doReturn(true).when(root.mAppCompatController
+                .getAppCompatAspectRatioOverrides()).shouldEnableUserAspectRatioSettings();
 
         // When in size compat mode the button is not enabled
         doReturn(true).when(root).inSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index a94b586..45dbea2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.WindowInsets.Type.systemOverlays;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -77,6 +78,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -91,6 +93,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -960,6 +964,25 @@
         assertTrue(child.handlesOrientationChangeFromDescendant(orientation));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+    public void testAddLocalInsets_addsFlagsFromProvider() {
+        final Task rootTask = createTask(mDisplayContent);
+        final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+
+        final Binder owner = new Binder();
+        Rect insetsRect = new Rect(0, 200, 1080, 700);
+        final int flags = FLAG_FORCE_CONSUMING;
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.captionBar())
+                        .setArbitraryRectangle(insetsRect)
+                        .setFlags(flags);
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        final int sourceFlags = task.mLocalInsetsSources.get(provider.getId()).getFlags();
+        assertEquals(flags, sourceFlags);
+    }
+
     private static void addLocalInsets(WindowContainer wc) {
         final Binder owner = new Binder();
         Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d3504cc..fb81a52 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
@@ -75,10 +76,12 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
 import android.util.Rational;
 import android.view.Display;
+import android.view.InsetsSource;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
 import android.window.ITaskFragmentOrganizer;
@@ -904,7 +907,8 @@
                 0 /* index */,
                 WindowInsets.Type.systemOverlays(),
                 new Rect(0, 0, 1080, 200),
-                null /* boundingRects */);
+                null /* boundingRects */,
+                0 /* flags */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSources
@@ -930,7 +934,8 @@
                 0 /* index */,
                 WindowInsets.Type.systemOverlays(),
                 new Rect(0, 0, 1080, 200),
-                boundingRects);
+                boundingRects,
+                0 /* flags */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         assertArrayEquals(boundingRects, navigationBarInsetsReceiverTask.mLocalInsetsSources
@@ -938,6 +943,30 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+    public void testAddInsetsSource_withFlags() {
+        final Task rootTask = createTask(mDisplayContent);
+
+        final Task insetsReceiverTask = createTaskInRootTask(rootTask, 0);
+        insetsReceiverTask.getConfiguration().windowConfiguration
+                .setBounds(new Rect(0, 200, 1080, 700));
+
+        final @InsetsSource.Flags int flags = FLAG_FORCE_CONSUMING;
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.addInsetsSource(
+                insetsReceiverTask.mRemoteToken.toWindowContainerToken(),
+                new Binder(),
+                0 /* index */,
+                WindowInsets.Type.systemOverlays(),
+                new Rect(0, 0, 1080, 200),
+                null /* boundingRects */,
+                flags);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+        assertEquals(flags, insetsReceiverTask.mLocalInsetsSources.valueAt(0).getFlags());
+    }
+
+    @Test
     public void testRemoveInsetsSource() {
         final Task rootTask = createTask(mDisplayContent);
 
@@ -952,7 +981,8 @@
                 0 /* index */,
                 WindowInsets.Type.systemOverlays(),
                 new Rect(0, 0, 1080, 200),
-                null /* boundingRects */);
+                null /* boundingRects */,
+                0 /* flags */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         final WindowContainerTransaction wct2 = new WindowContainerTransaction();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e6648da..0cb22ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -86,6 +87,7 @@
 
         ApplicationInfo info = mock(ApplicationInfo.class);
         info.packageName = "test.package.name";
+        doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
         mWpc = new WindowProcessController(
                 mAtm, info, null, 0, -1, null, mMockListener);
         mWpc.setThread(mock(IApplicationThread.class));
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index 49e9232..14c9ea5 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -159,7 +159,7 @@
     @Override
     public String toString() {
         String bluetoothDeviceList = supportedBluetoothDevices.stream()
-                .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+                .map(BluetoothDevice::toString).collect(Collectors.joining(", "));
 
         return String.format(Locale.US,
                 "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index ccc3683..78d93e1 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -34,6 +34,11 @@
     srcs: ["src/**/Close*"],
 }
 
+filegroup {
+    name: "FlickerTestsIme2-src",
+    srcs: ["src/**/ShowImeOnAppStart*"],
+}
+
 android_test {
     name: "FlickerTestsIme",
     defaults: ["FlickerTestsDefault"],
@@ -77,9 +82,23 @@
     defaults: ["FlickerTestsDefault"],
     manifest: "AndroidManifest.xml",
     test_config_template: "AndroidTestTemplate.xml",
+    srcs: [":FlickerTestsIme2-src"],
+    static_libs: [
+        "FlickerTestsBase",
+        "FlickerTestsImeCommon",
+    ],
+    data: ["trace_config/*"],
+}
+
+android_test {
+    name: "FlickerTestsIme3",
+    defaults: ["FlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: ["src/**/*"],
     exclude_srcs: [
         ":FlickerTestsIme1-src",
+        ":FlickerTestsIme2-src",
         ":FlickerTestsImeCommon-src",
     ],
     static_libs: [
diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml
index 8db3705..4a99bd4 100644
--- a/tests/Input/AndroidTest.xml
+++ b/tests/Input/AndroidTest.xml
@@ -31,7 +31,7 @@
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="pull-pattern-keys" value="input_.*" />
         <!-- Pull files created by tests, like the output of screenshot tests -->
-        <option name="directory-keys" value="/storage/emulated/0/InputTests" />
+        <option name="directory-keys" value="/sdcard/Download/InputTests" />
         <option name="collect-on-run-ended-only" value="false" />
     </metrics_collector>
 </configuration>
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
index e0f8c6d..d0148fb 100644
--- a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.content.res.Resources
-import android.os.Environment
 import android.view.ContextThemeWrapper
 import android.view.PointerIcon
 import android.view.flags.Flags.enableVectorCursorA11ySettings
@@ -158,8 +157,7 @@
         const val SCREEN_WIDTH_DP = 480
         const val SCREEN_HEIGHT_DP = 800
         const val ASSETS_PATH = "tests/input/assets"
-        val TEST_OUTPUT_PATH = Environment.getExternalStorageDirectory().absolutePath +
-                "/InputTests/" +
-                PointerIconLoadingTest::class.java.simpleName
+        val TEST_OUTPUT_PATH =
+            "/sdcard/Download/InputTests/" + PointerIconLoadingTest::class.java.simpleName
     }
 }
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index df23745..8433071 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -54,6 +54,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -110,6 +111,7 @@
         assertPassengerImeHidden();
     }
 
+    @Ignore("b/350562427")
     @Test
     public void passengerShowImeNotAffectDriver() throws Exception {
         assertDriverImeHidden();
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index ee53af1..012b0f2 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -116,6 +116,10 @@
                       "This decreases APK size at the cost of resource retrieval performance.\n"
                       "Applies sparse encoding to all resources regardless of minSdk.",
                       &options_.force_sparse_encoding);
+    AddOptionalSwitch(
+        "--enable-compact-entries",
+        "This decreases APK size by using compact resource entries for simple data types.",
+        &options_.table_flattener_options.use_compact_entries);
     AddOptionalSwitch("--collapse-resource-names",
         "Collapses resource names to a single value in the key string pool. Resources can \n"
             "be exempted using the \"no_collapse\" directive in a file specified by "
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index 191f38d..718d898 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -62,13 +62,11 @@
                         XmlUtils.getSingleChildElement(
                                 document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES, true);
 
-                return new AndroidSafetyLabelFactory()
-                        .createFromHrElements(XmlUtils.listOf(appMetadataBundles));
+                return new AndroidSafetyLabelFactory().createFromHrElement(appMetadataBundles);
             case ON_DEVICE:
                 Element bundleEle =
                         XmlUtils.getSingleChildElement(document, XmlUtils.OD_TAG_BUNDLE, true);
-                return new AndroidSafetyLabelFactory()
-                        .createFromOdElements(XmlUtils.listOf(bundleEle));
+                return new AndroidSafetyLabelFactory().createFromOdElement(bundleEle);
             default:
                 throw new IllegalStateException("Unrecognized input format.");
         }
@@ -91,14 +89,10 @@
 
         switch (format) {
             case HUMAN_READABLE:
-                for (var child : asl.toHrDomElements(document)) {
-                    document.appendChild(child);
-                }
+                document.appendChild(asl.toHrDomElement(document));
                 break;
             case ON_DEVICE:
-                for (var child : asl.toOdDomElements(document)) {
-                    document.appendChild(child);
-                }
+                document.appendChild(asl.toOdDomElement(document));
                 break;
             default:
                 throw new IllegalStateException("Unrecognized input format.");
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index 72140a1..8b2fd93 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -21,8 +21,6 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 public class AndroidSafetyLabel implements AslMarshallable {
 
     private final Long mVersion;
@@ -46,36 +44,34 @@
     }
 
     /** Creates an on-device DOM element from an {@link AndroidSafetyLabel} */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE);
         aslEle.appendChild(XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
         if (mSafetyLabels != null) {
-            XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
+            aslEle.appendChild(mSafetyLabels.toOdDomElement(doc));
         }
         if (mSystemAppSafetyLabel != null) {
-            XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toOdDomElements(doc));
+            aslEle.appendChild(mSystemAppSafetyLabel.toOdDomElement(doc));
         }
         if (mTransparencyInfo != null) {
-            XmlUtils.appendChildren(aslEle, mTransparencyInfo.toOdDomElements(doc));
+            aslEle.appendChild(mTransparencyInfo.toOdDomElement(doc));
         }
-        return XmlUtils.listOf(aslEle);
+        return aslEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element aslEle = doc.createElement(XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
         aslEle.setAttribute(XmlUtils.HR_ATTR_VERSION, String.valueOf(mVersion));
         if (mSafetyLabels != null) {
-            XmlUtils.appendChildren(aslEle, mSafetyLabels.toHrDomElements(doc));
+            aslEle.appendChild(mSafetyLabels.toHrDomElement(doc));
         }
         if (mSystemAppSafetyLabel != null) {
-            XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toHrDomElements(doc));
+            aslEle.appendChild(mSystemAppSafetyLabel.toHrDomElement(doc));
         }
         if (mTransparencyInfo != null) {
-            XmlUtils.appendChildren(aslEle, mTransparencyInfo.toHrDomElements(doc));
+            aslEle.appendChild(mTransparencyInfo.toHrDomElement(doc));
         }
-        return XmlUtils.listOf(aslEle);
+        return aslEle;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index c53cbbf..b9eb2a35 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -21,65 +21,117 @@
 
 import org.w3c.dom.Element;
 
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class AndroidSafetyLabelFactory implements AslMarshallableFactory<AndroidSafetyLabel> {
+    private final Map<Long, Set<String>> mRecognizedHrAttrs =
+            Map.ofEntries(Map.entry(1L, Set.of(XmlUtils.HR_ATTR_VERSION)));
+    private final Map<Long, Set<String>> mRequiredHrAttrs =
+            Map.ofEntries(Map.entry(1L, Set.of(XmlUtils.HR_ATTR_VERSION)));
+    private final Map<Long, Set<String>> mRecognizedHrEles =
+            Map.ofEntries(
+                    Map.entry(
+                            1L,
+                            Set.of(
+                                    XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL,
+                                    XmlUtils.HR_TAG_SAFETY_LABELS,
+                                    XmlUtils.HR_TAG_TRANSPARENCY_INFO)));
+    private final Map<Long, Set<String>> mRequiredHrEles =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of()),
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL,
+                                    XmlUtils.HR_TAG_TRANSPARENCY_INFO)));
+    private final Map<Long, Set<String>> mRecognizedOdEleNames =
+            Map.ofEntries(
+                    Map.entry(
+                            1L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_VERSION,
+                                    XmlUtils.OD_NAME_SAFETY_LABELS,
+                                    XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL,
+                                    XmlUtils.OD_NAME_TRANSPARENCY_INFO)));
+    private final Map<Long, Set<String>> mRequiredOdEleNames =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of(XmlUtils.OD_NAME_VERSION)),
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_VERSION,
+                                    XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL,
+                                    XmlUtils.OD_NAME_TRANSPARENCY_INFO)));
 
     /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */
-    @Override
-    public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles)
+    public AndroidSafetyLabel createFromHrElement(Element appMetadataBundlesEle)
             throws MalformedXmlException {
-        Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles);
         long version = XmlUtils.tryGetVersion(appMetadataBundlesEle);
+        XmlUtils.throwIfExtraneousAttributes(
+                appMetadataBundlesEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+        XmlUtils.throwIfExtraneousChildrenHr(
+                appMetadataBundlesEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
 
         Element safetyLabelsEle =
                 XmlUtils.getSingleChildElement(
-                        appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS, false);
+                        appMetadataBundlesEle,
+                        XmlUtils.HR_TAG_SAFETY_LABELS,
+                        XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
         SafetyLabels safetyLabels =
-                new SafetyLabelsFactory().createFromHrElements(XmlUtils.listOf(safetyLabelsEle));
+                new SafetyLabelsFactory().createFromHrElement(safetyLabelsEle, version);
 
         Element systemAppSafetyLabelEle =
                 XmlUtils.getSingleChildElement(
-                        appMetadataBundlesEle, XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL, false);
+                        appMetadataBundlesEle,
+                        XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL,
+                        XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
         SystemAppSafetyLabel systemAppSafetyLabel =
                 new SystemAppSafetyLabelFactory()
-                        .createFromHrElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+                        .createFromHrElement(systemAppSafetyLabelEle, version);
 
         Element transparencyInfoEle =
                 XmlUtils.getSingleChildElement(
-                        appMetadataBundlesEle, XmlUtils.HR_TAG_TRANSPARENCY_INFO, false);
+                        appMetadataBundlesEle,
+                        XmlUtils.HR_TAG_TRANSPARENCY_INFO,
+                        XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
         TransparencyInfo transparencyInfo =
-                new TransparencyInfoFactory()
-                        .createFromHrElements(XmlUtils.listOf(transparencyInfoEle));
+                new TransparencyInfoFactory().createFromHrElement(transparencyInfoEle, version);
 
         return new AndroidSafetyLabel(
                 version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
     }
 
     /** Creates an {@link AndroidSafetyLabel} from on-device DOM elements */
-    @Override
-    public AndroidSafetyLabel createFromOdElements(List<Element> elements)
-            throws MalformedXmlException {
-        Element bundleEle = XmlUtils.getSingleElement(elements);
+    public AndroidSafetyLabel createFromOdElement(Element bundleEle) throws MalformedXmlException {
         Long version = XmlUtils.getOdLongEle(bundleEle, XmlUtils.OD_NAME_VERSION, true);
+        XmlUtils.throwIfExtraneousChildrenOd(
+                bundleEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
 
         Element safetyLabelsEle =
-                XmlUtils.getOdPbundleWithName(bundleEle, XmlUtils.OD_NAME_SAFETY_LABELS, false);
+                XmlUtils.getOdPbundleWithName(
+                        bundleEle,
+                        XmlUtils.OD_NAME_SAFETY_LABELS,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
         SafetyLabels safetyLabels =
-                new SafetyLabelsFactory().createFromOdElements(XmlUtils.listOf(safetyLabelsEle));
+                new SafetyLabelsFactory().createFromOdElement(safetyLabelsEle, version);
 
         Element systemAppSafetyLabelEle =
                 XmlUtils.getOdPbundleWithName(
-                        bundleEle, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL, false);
+                        bundleEle,
+                        XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
         SystemAppSafetyLabel systemAppSafetyLabel =
                 new SystemAppSafetyLabelFactory()
-                        .createFromOdElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+                        .createFromOdElement(systemAppSafetyLabelEle, version);
 
         Element transparencyInfoEle =
-                XmlUtils.getOdPbundleWithName(bundleEle, XmlUtils.OD_NAME_TRANSPARENCY_INFO, false);
+                XmlUtils.getOdPbundleWithName(
+                        bundleEle,
+                        XmlUtils.OD_NAME_TRANSPARENCY_INFO,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
         TransparencyInfo transparencyInfo =
-                new TransparencyInfoFactory()
-                        .createFromOdElements(XmlUtils.listOf(transparencyInfoEle));
+                new TransparencyInfoFactory().createFromOdElement(transparencyInfoEle, version);
 
         return new AndroidSafetyLabel(
                 version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index f397225..d2557ae 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -25,36 +25,107 @@
 
 /** AppInfo representation */
 public class AppInfo implements AslMarshallable {
-    private final Boolean mApsCompliant;
+    private final String mTitle;
+    private final String mDescription;
+    private final Boolean mContainsAds;
+    private final Boolean mObeyAps;
+    private final Boolean mAdsFingerprinting;
+    private final Boolean mSecurityFingerprinting;
     private final String mPrivacyPolicy;
+    private final List<String> mSecurityEndpoints;
     private final List<String> mFirstPartyEndpoints;
     private final List<String> mServiceProviderEndpoints;
+    private final String mCategory;
+    private final String mEmail;
+    private final String mWebsite;
+
+    private final Boolean mApsCompliant;
+    private final String mDeveloperId;
+    private final String mApplicationId;
+
+    // private final String mPrivacyPolicy;
+    // private final List<String> mFirstPartyEndpoints;
+    // private final List<String> mServiceProviderEndpoints;
 
     public AppInfo(
-            Boolean apsCompliant,
+            String title,
+            String description,
+            Boolean containsAds,
+            Boolean obeyAps,
+            Boolean adsFingerprinting,
+            Boolean securityFingerprinting,
             String privacyPolicy,
+            List<String> securityEndpoints,
             List<String> firstPartyEndpoints,
-            List<String> serviceProviderEndpoints) {
-        this.mApsCompliant = apsCompliant;
+            List<String> serviceProviderEndpoints,
+            String category,
+            String email,
+            String website,
+            Boolean apsCompliant,
+            String developerId,
+            String applicationId) {
+        this.mTitle = title;
+        this.mDescription = description;
+        this.mContainsAds = containsAds;
+        this.mObeyAps = obeyAps;
+        this.mAdsFingerprinting = adsFingerprinting;
+        this.mSecurityFingerprinting = securityFingerprinting;
         this.mPrivacyPolicy = privacyPolicy;
+        this.mSecurityEndpoints = securityEndpoints;
         this.mFirstPartyEndpoints = firstPartyEndpoints;
         this.mServiceProviderEndpoints = serviceProviderEndpoints;
+        this.mCategory = category;
+        this.mEmail = email;
+        this.mWebsite = website;
+        this.mApsCompliant = apsCompliant;
+        this.mDeveloperId = developerId;
+        this.mApplicationId = applicationId;
     }
 
     /** Creates an on-device DOM element from the {@link SafetyLabels}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element appInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_APP_INFO);
-        if (this.mApsCompliant != null) {
+
+        if (this.mTitle != null) {
+            appInfoEle.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_TITLE, mTitle));
+        }
+        if (this.mDescription != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DESCRIPTION, mDescription));
+        }
+        if (this.mContainsAds != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_CONTAINS_ADS, mContainsAds));
+        }
+        if (this.mObeyAps != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_OBEY_APS, mObeyAps));
+        }
+        if (this.mAdsFingerprinting != null) {
             appInfoEle.appendChild(
                     XmlUtils.createOdBooleanEle(
-                            doc, XmlUtils.OD_NAME_APS_COMPLIANT, mApsCompliant));
+                            doc, XmlUtils.OD_NAME_ADS_FINGERPRINTING, mAdsFingerprinting));
+        }
+        if (this.mSecurityFingerprinting != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(
+                            doc,
+                            XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+                            mSecurityFingerprinting));
         }
         if (this.mPrivacyPolicy != null) {
             appInfoEle.appendChild(
                     XmlUtils.createOdStringEle(
                             doc, XmlUtils.OD_NAME_PRIVACY_POLICY, mPrivacyPolicy));
         }
+        if (this.mSecurityEndpoints != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdArray(
+                            doc,
+                            XmlUtils.OD_TAG_STRING_ARRAY,
+                            XmlUtils.OD_NAME_SECURITY_ENDPOINTS,
+                            mSecurityEndpoints));
+        }
         if (this.mFirstPartyEndpoints != null) {
             appInfoEle.appendChild(
                     XmlUtils.createOdArray(
@@ -71,27 +142,88 @@
                             XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
                             mServiceProviderEndpoints));
         }
-        return XmlUtils.listOf(appInfoEle);
+        if (this.mCategory != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_CATEGORY, this.mCategory));
+        }
+        if (this.mEmail != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, this.mEmail));
+        }
+        if (this.mWebsite != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, this.mWebsite));
+        }
+
+        if (this.mApsCompliant != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(
+                            doc, XmlUtils.OD_NAME_APS_COMPLIANT, mApsCompliant));
+        }
+        if (this.mDeveloperId != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DEVELOPER_ID, mDeveloperId));
+        }
+        if (this.mApplicationId != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(
+                            doc, XmlUtils.OD_NAME_APPLICATION_ID, mApplicationId));
+        }
+        return appInfoEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element appInfoEle = doc.createElement(XmlUtils.HR_TAG_APP_INFO);
-        if (this.mApsCompliant != null) {
+
+        if (this.mTitle != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_TITLE, this.mTitle);
+        }
+        if (this.mDescription != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_DESCRIPTION, this.mDescription);
+        }
+        if (this.mContainsAds != null) {
             appInfoEle.setAttribute(
-                    XmlUtils.HR_ATTR_APS_COMPLIANT, String.valueOf(this.mApsCompliant));
+                    XmlUtils.HR_ATTR_CONTAINS_ADS, String.valueOf(this.mContainsAds));
+        }
+        if (this.mObeyAps != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_OBEY_APS, String.valueOf(this.mObeyAps));
+        }
+        if (this.mAdsFingerprinting != null) {
+            appInfoEle.setAttribute(
+                    XmlUtils.HR_ATTR_ADS_FINGERPRINTING, String.valueOf(this.mAdsFingerprinting));
+        }
+        if (this.mSecurityFingerprinting != null) {
+            appInfoEle.setAttribute(
+                    XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING,
+                    String.valueOf(this.mSecurityFingerprinting));
         }
         if (this.mPrivacyPolicy != null) {
             appInfoEle.setAttribute(XmlUtils.HR_ATTR_PRIVACY_POLICY, this.mPrivacyPolicy);
         }
+        if (this.mSecurityEndpoints != null) {
+            appInfoEle.setAttribute(
+                    XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, String.join("|", this.mSecurityEndpoints));
+        }
+        if (this.mCategory != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_CATEGORY, this.mCategory);
+        }
+        if (this.mEmail != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, this.mEmail);
+        }
+        if (this.mWebsite != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, this.mWebsite);
+        }
 
+        if (this.mApsCompliant != null) {
+            appInfoEle.setAttribute(
+                    XmlUtils.HR_ATTR_APS_COMPLIANT, String.valueOf(this.mApsCompliant));
+        }
         if (this.mFirstPartyEndpoints != null) {
             appInfoEle.appendChild(
                     XmlUtils.createHrArray(
                             doc, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, mFirstPartyEndpoints));
         }
-
         if (this.mServiceProviderEndpoints != null) {
             appInfoEle.appendChild(
                     XmlUtils.createHrArray(
@@ -99,7 +231,13 @@
                             XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS,
                             mServiceProviderEndpoints));
         }
+        if (this.mDeveloperId != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_DEVELOPER_ID, this.mDeveloperId);
+        }
+        if (this.mApplicationId != null) {
+            appInfoEle.setAttribute(XmlUtils.HR_ATTR_APPLICATION_ID, this.mApplicationId);
+        }
 
-        return XmlUtils.listOf(appInfoEle);
+        return appInfoEle;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 6ad2027..277a508 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -16,60 +16,233 @@
 
 package com.android.asllib.marshallable;
 
-import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Element;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
+    // We don't need to support V1 for HR.
+    private final Map<Long, Set<String>> mRecognizedHrAttrs =
+            Map.ofEntries(
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.HR_ATTR_APS_COMPLIANT,
+                                    XmlUtils.HR_ATTR_PRIVACY_POLICY,
+                                    XmlUtils.HR_ATTR_DEVELOPER_ID,
+                                    XmlUtils.HR_ATTR_APPLICATION_ID)));
+    private final Map<Long, Set<String>> mRequiredHrAttrs =
+            Map.ofEntries(
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.HR_ATTR_APS_COMPLIANT,
+                                    XmlUtils.HR_ATTR_PRIVACY_POLICY,
+                                    XmlUtils.HR_ATTR_DEVELOPER_ID,
+                                    XmlUtils.HR_ATTR_APPLICATION_ID)));
+    private final Map<Long, Set<String>> mRecognizedHrEles =
+            Map.ofEntries(
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS,
+                                    XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS)));
+    private final Map<Long, Set<String>> mRequiredHrEles =
+            Map.ofEntries(
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS,
+                                    XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS)));
+    private final Map<Long, Set<String>> mRecognizedOdEleNames =
+            Map.ofEntries(
+                    Map.entry(
+                            1L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_TITLE,
+                                    XmlUtils.OD_NAME_DESCRIPTION,
+                                    XmlUtils.OD_NAME_CONTAINS_ADS,
+                                    XmlUtils.OD_NAME_OBEY_APS,
+                                    XmlUtils.OD_NAME_ADS_FINGERPRINTING,
+                                    XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+                                    XmlUtils.OD_NAME_PRIVACY_POLICY,
+                                    XmlUtils.OD_NAME_SECURITY_ENDPOINTS,
+                                    XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+                                    XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+                                    XmlUtils.OD_NAME_CATEGORY,
+                                    XmlUtils.OD_NAME_EMAIL,
+                                    XmlUtils.OD_NAME_WEBSITE)),
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_APS_COMPLIANT,
+                                    XmlUtils.OD_NAME_PRIVACY_POLICY,
+                                    XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+                                    XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+                                    XmlUtils.OD_NAME_DEVELOPER_ID,
+                                    XmlUtils.OD_NAME_APPLICATION_ID)));
+    private final Map<Long, Set<String>> mRequiredOdEles =
+            Map.ofEntries(
+                    Map.entry(
+                            1L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_TITLE,
+                                    XmlUtils.OD_NAME_DESCRIPTION,
+                                    XmlUtils.OD_NAME_CONTAINS_ADS,
+                                    XmlUtils.OD_NAME_OBEY_APS,
+                                    XmlUtils.OD_NAME_ADS_FINGERPRINTING,
+                                    XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+                                    XmlUtils.OD_NAME_PRIVACY_POLICY,
+                                    XmlUtils.OD_NAME_SECURITY_ENDPOINTS,
+                                    XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+                                    XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+                                    XmlUtils.OD_NAME_CATEGORY)),
+                    Map.entry(
+                            2L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_APS_COMPLIANT,
+                                    XmlUtils.OD_NAME_PRIVACY_POLICY,
+                                    XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+                                    XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+                                    XmlUtils.OD_NAME_DEVELOPER_ID,
+                                    XmlUtils.OD_NAME_APPLICATION_ID)));
 
     /** Creates a {@link AppInfo} from the human-readable DOM element. */
-    @Override
-    public AppInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
-        Element appInfoEle = XmlUtils.getSingleElement(elements);
-        if (appInfoEle == null) {
-            AslgenUtil.logI("No AppInfo found in hr format.");
-            return null;
-        }
+    public AppInfo createFromHrElement(Element appInfoEle, long version)
+            throws MalformedXmlException {
+        XmlUtils.throwIfExtraneousAttributes(
+                appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+        XmlUtils.throwIfExtraneousChildrenHr(
+                appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
+
+        var requiredHrAttrs = XmlUtils.getMostRecentVersion(mRequiredHrAttrs, version);
+        var requiredHrEles = XmlUtils.getMostRecentVersion(mRequiredHrEles, version);
+
+        String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE, requiredHrAttrs);
+        String description =
+                XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION, requiredHrAttrs);
+        Boolean containsAds =
+                XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, requiredHrAttrs);
+        Boolean obeyAps =
+                XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, requiredHrAttrs);
+        Boolean adsFingerprinting =
+                XmlUtils.getBoolAttr(
+                        appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, requiredHrAttrs);
+        Boolean securityFingerprinting =
+                XmlUtils.getBoolAttr(
+                        appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, requiredHrAttrs);
+        String privacyPolicy =
+                XmlUtils.getStringAttr(
+                        appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, requiredHrAttrs);
+        List<String> securityEndpoints =
+                XmlUtils.getPipelineSplitAttr(
+                        appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, requiredHrAttrs);
+        String category =
+                XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY, requiredHrAttrs);
+        String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL, requiredHrAttrs);
+        String website =
+                XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, requiredHrAttrs);
+        String developerId =
+                XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DEVELOPER_ID, requiredHrAttrs);
+        String applicationId =
+                XmlUtils.getStringAttr(
+                        appInfoEle, XmlUtils.HR_ATTR_APPLICATION_ID, requiredHrAttrs);
 
         Boolean apsCompliant =
-                XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_APS_COMPLIANT, true);
-        String privacyPolicy =
-                XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, true);
+                XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_APS_COMPLIANT, requiredHrAttrs);
         List<String> firstPartyEndpoints =
                 XmlUtils.getHrItemsAsStrings(
-                        appInfoEle, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, true);
+                        appInfoEle, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, requiredHrEles);
         List<String> serviceProviderEndpoints =
                 XmlUtils.getHrItemsAsStrings(
-                        appInfoEle, XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, true);
+                        appInfoEle, XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, requiredHrEles);
 
         return new AppInfo(
-                apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints);
+                title,
+                description,
+                containsAds,
+                obeyAps,
+                adsFingerprinting,
+                securityFingerprinting,
+                privacyPolicy,
+                securityEndpoints,
+                firstPartyEndpoints,
+                serviceProviderEndpoints,
+                category,
+                email,
+                website,
+                apsCompliant,
+                developerId,
+                applicationId);
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public AppInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
-        Element appInfoEle = XmlUtils.getSingleElement(elements);
-        if (appInfoEle == null) {
-            AslgenUtil.logI("No AppInfo found in od format.");
-            return null;
-        }
+    public AppInfo createFromOdElement(Element appInfoEle, long version)
+            throws MalformedXmlException {
+        XmlUtils.throwIfExtraneousChildrenOd(
+                appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+        var requiredOdEles = XmlUtils.getMostRecentVersion(mRequiredOdEles, version);
+
+        String title = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_TITLE, requiredOdEles);
+        String description =
+                XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DESCRIPTION, requiredOdEles);
+        Boolean containsAds =
+                XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_CONTAINS_ADS, requiredOdEles);
+        Boolean obeyAps =
+                XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_OBEY_APS, requiredOdEles);
+        Boolean adsFingerprinting =
+                XmlUtils.getOdBoolEle(
+                        appInfoEle, XmlUtils.OD_NAME_ADS_FINGERPRINTING, requiredOdEles);
+        Boolean securityFingerprinting =
+                XmlUtils.getOdBoolEle(
+                        appInfoEle, XmlUtils.OD_NAME_SECURITY_FINGERPRINTING, requiredOdEles);
+        String privacyPolicy =
+                XmlUtils.getOdStringEle(
+                        appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, requiredOdEles);
+        List<String> securityEndpoints =
+                XmlUtils.getOdStringArray(
+                        appInfoEle, XmlUtils.OD_NAME_SECURITY_ENDPOINTS, requiredOdEles);
+        String category =
+                XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_CATEGORY, requiredOdEles);
+        String email = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_EMAIL, requiredOdEles);
+        String website =
+                XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_WEBSITE, requiredOdEles);
+        String developerId =
+                XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DEVELOPER_ID, requiredOdEles);
+        String applicationId =
+                XmlUtils.getOdStringEle(
+                        appInfoEle, XmlUtils.OD_NAME_APPLICATION_ID, requiredOdEles);
 
         Boolean apsCompliant =
-                XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_APS_COMPLIANT, true);
-        String privacyPolicy =
-                XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, true);
+                XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_APS_COMPLIANT, requiredOdEles);
         List<String> firstPartyEndpoints =
-                XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, true);
+                XmlUtils.getOdStringArray(
+                        appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, requiredOdEles);
         List<String> serviceProviderEndpoints =
                 XmlUtils.getOdStringArray(
-                        appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, true);
+                        appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, requiredOdEles);
 
         return new AppInfo(
-                apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints);
+                title,
+                description,
+                containsAds,
+                obeyAps,
+                adsFingerprinting,
+                securityFingerprinting,
+                privacyPolicy,
+                securityEndpoints,
+                firstPartyEndpoints,
+                serviceProviderEndpoints,
+                category,
+                email,
+                website,
+                apsCompliant,
+                developerId,
+                applicationId);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
index 0a70e7d0..b6c789d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
@@ -16,16 +16,11 @@
 
 package com.android.asllib.marshallable;
 
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.util.List;
-
 public interface AslMarshallable {
 
     /** Creates the on-device DOM elements from the AslMarshallable Java Object. */
-    List<Element> toOdDomElements(Document doc);
+    // List<Element> toOdDomElements(Document doc);
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    List<Element> toHrDomElements(Document doc);
+    // List<Element> toHrDomElements(Document doc);
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
index 3958290..67f1069 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
@@ -16,17 +16,11 @@
 
 package com.android.asllib.marshallable;
 
-import com.android.asllib.util.MalformedXmlException;
-
-import org.w3c.dom.Element;
-
-import java.util.List;
-
 public interface AslMarshallableFactory<T extends AslMarshallable> {
 
     /** Creates an {@link AslMarshallableFactory} from human-readable DOM elements */
-    T createFromHrElements(List<Element> elements) throws MalformedXmlException;
+    // T createFromHrElements(List<Element> elements) throws MalformedXmlException;
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    T createFromOdElements(List<Element> elements) throws MalformedXmlException;
+    // T createFromOdElements(List<Element> elements) throws MalformedXmlException;
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index c16d18b..501f170 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -57,18 +57,16 @@
     }
 
     /** Creates on-device DOM element(s) from the {@link DataCategory}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element dataCategoryEle = XmlUtils.createPbundleEleWithName(doc, this.getCategoryName());
         for (DataType dataType : mDataTypes.values()) {
-            XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
+            dataCategoryEle.appendChild(dataType.toOdDomElement(doc));
         }
-        return XmlUtils.listOf(dataCategoryEle);
+        return dataCategoryEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public List<Element> toHrDomElement(Document doc) {
         throw new IllegalStateException(
                 "Turning DataCategory or DataType into human-readable DOM elements requires"
                         + " visibility into parent elements. The logic resides in DataLabels.");
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index 7244162..fb84e50 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -23,13 +23,11 @@
 import org.w3c.dom.Element;
 
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
 public class DataCategoryFactory {
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException {
-        Element dataCategoryEle = XmlUtils.getSingleElement(elements);
+    public DataCategory createFromOdElement(Element dataCategoryEle) throws MalformedXmlException {
         Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
         String categoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
         var odDataTypes = XmlUtils.asElementList(dataCategoryEle.getChildNodes());
@@ -45,9 +43,7 @@
                                 "Unrecognized data type name %s for category %s",
                                 dataTypeName, categoryName));
             }
-            dataTypeMap.put(
-                    dataTypeName,
-                    new DataTypeFactory().createFromOdElements(XmlUtils.listOf(odDataTypeEle)));
+            dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromOdElement(odDataTypeEle));
         }
 
         return new DataCategory(categoryName, dataTypeMap);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 3c93c88..2cf7c82f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -22,7 +22,6 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -57,20 +56,18 @@
     }
 
     /** Gets the on-device DOM element for the {@link DataLabels}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element dataLabelsEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
 
         maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
         maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
 
-        return XmlUtils.listOf(dataLabelsEle);
+        return dataLabelsEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS);
         maybeAppendHrDataUsages(
                 doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED, false);
@@ -78,7 +75,7 @@
                 doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED_EPHEMERAL, true);
         maybeAppendHrDataUsages(
                 doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED, false);
-        return XmlUtils.listOf(dataLabelsEle);
+        return dataLabelsEle;
     }
 
     private void maybeAppendDataUsages(
@@ -96,7 +93,7 @@
             DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName);
             for (String dataTypeName : dataCategory.getDataTypes().keySet()) {
                 DataType dataType = dataCategory.getDataTypes().get(dataTypeName);
-                XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
+                dataCategoryEle.appendChild(dataType.toOdDomElement(doc));
             }
             dataUsageEle.appendChild(dataCategoryEle);
         }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index c4d8876..b1cf3ea0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -31,9 +31,7 @@
 public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
 
     /** Creates a {@link DataLabels} from the human-readable DOM element. */
-    @Override
-    public DataLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
-        Element ele = XmlUtils.getSingleElement(elements);
+    public DataLabels createFromHrElement(Element ele) throws MalformedXmlException {
         if (ele == null) {
             AslgenUtil.logI("Found no DataLabels in hr format.");
             return null;
@@ -83,9 +81,7 @@
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public DataLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
-        Element dataLabelsEle = XmlUtils.getSingleElement(elements);
+    public DataLabels createFromOdElement(Element dataLabelsEle) throws MalformedXmlException {
         if (dataLabelsEle == null) {
             AslgenUtil.logI("Found no DataLabels in od format.");
             return null;
@@ -111,7 +107,7 @@
         for (Element dataCategoryEle : dataCategoryEles) {
             String dataCategoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
             DataCategory dataCategory =
-                    new DataCategoryFactory().createFromOdElements(List.of(dataCategoryEle));
+                    new DataCategoryFactory().createFromOdElement(dataCategoryEle);
             dataCategoryMap.put(dataCategoryName, dataCategory);
         }
         return dataCategoryMap;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index 284a4b8..83bb611 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -172,8 +172,8 @@
         return mEphemeral;
     }
 
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    /** Gets the on-device dom element */
+    public Element toOdDomElement(Document doc) {
         Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, this.getDataTypeName());
         if (!this.getPurposes().isEmpty()) {
             dataTypeEle.appendChild(
@@ -197,11 +197,10 @@
                 this.getIsSharingOptional(),
                 XmlUtils.OD_NAME_IS_SHARING_OPTIONAL);
         maybeAddBoolToOdElement(doc, dataTypeEle, this.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL);
-        return XmlUtils.listOf(dataTypeEle);
+        return dataTypeEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
     public List<Element> toHrDomElements(Document doc) {
         throw new IllegalStateException(
                 "Turning DataCategory or DataType into human-readable DOM elements requires"
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index a5559d8..96a58fa 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -64,8 +64,7 @@
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException {
-        Element odDataTypeEle = XmlUtils.getSingleElement(elements);
+    public DataType createFromOdElement(Element odDataTypeEle) throws MalformedXmlException {
         String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
         List<Integer> purposeInts =
                 XmlUtils.getOdIntArray(odDataTypeEle, XmlUtils.OD_NAME_PURPOSES, true);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
new file mode 100644
index 0000000..96a64dc
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 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.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/** DeveloperInfo representation */
+public class DeveloperInfo implements AslMarshallable {
+    public enum DeveloperRelationship {
+        OEM(0),
+        ODM(1),
+        SOC(2),
+        OTA(3),
+        CARRIER(4),
+        AOSP(5),
+        OTHER(6);
+
+        private final int mValue;
+
+        DeveloperRelationship(int value) {
+            this.mValue = value;
+        }
+
+        /** Get the int value associated with the DeveloperRelationship. */
+        public int getValue() {
+            return mValue;
+        }
+
+        /** Get the DeveloperRelationship associated with the int value. */
+        public static DeveloperInfo.DeveloperRelationship forValue(int value) {
+            for (DeveloperInfo.DeveloperRelationship e : values()) {
+                if (e.getValue() == value) {
+                    return e;
+                }
+            }
+            throw new IllegalArgumentException("No DeveloperRelationship enum for value: " + value);
+        }
+
+        /** Get the DeveloperRelationship associated with the human-readable String. */
+        public static DeveloperInfo.DeveloperRelationship forString(String s) {
+            for (DeveloperInfo.DeveloperRelationship e : values()) {
+                if (e.toString().equals(s)) {
+                    return e;
+                }
+            }
+            throw new IllegalArgumentException("No DeveloperRelationship enum for str: " + s);
+        }
+
+        /** Human-readable String representation of DeveloperRelationship. */
+        public String toString() {
+            return this.name().toLowerCase();
+        }
+    }
+
+    private final String mName;
+    private final String mEmail;
+    private final String mAddress;
+    private final String mCountryRegion;
+    private final DeveloperRelationship mDeveloperRelationship;
+    private final String mWebsite;
+    private final String mAppDeveloperRegistryId;
+
+    public DeveloperInfo(
+            String name,
+            String email,
+            String address,
+            String countryRegion,
+            DeveloperRelationship developerRelationship,
+            String website,
+            String appDeveloperRegistryId) {
+        this.mName = name;
+        this.mEmail = email;
+        this.mAddress = address;
+        this.mCountryRegion = countryRegion;
+        this.mDeveloperRelationship = developerRelationship;
+        this.mWebsite = website;
+        this.mAppDeveloperRegistryId = appDeveloperRegistryId;
+    }
+
+    /** Creates an on-device DOM element from the {@link SafetyLabels}. */
+    public Element toOdDomElement(Document doc) {
+        Element developerInfoEle =
+                XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DEVELOPER_INFO);
+        if (mName != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_NAME, mName));
+        }
+        if (mEmail != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, mEmail));
+        }
+        if (mAddress != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_ADDRESS, mAddress));
+        }
+        if (mCountryRegion != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(
+                            doc, XmlUtils.OD_NAME_COUNTRY_REGION, mCountryRegion));
+        }
+        if (mDeveloperRelationship != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdLongEle(
+                            doc,
+                            XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+                            mDeveloperRelationship.getValue()));
+        }
+        if (mWebsite != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, mWebsite));
+        }
+        if (mAppDeveloperRegistryId != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(
+                            doc,
+                            XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID,
+                            mAppDeveloperRegistryId));
+        }
+        return developerInfoEle;
+    }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    public Element toHrDomElement(Document doc) {
+        Element developerInfoEle = doc.createElement(XmlUtils.HR_TAG_DEVELOPER_INFO);
+        if (mName != null) {
+            developerInfoEle.setAttribute(XmlUtils.HR_ATTR_NAME, mName);
+        }
+        if (mEmail != null) {
+            developerInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, mEmail);
+        }
+        if (mAddress != null) {
+            developerInfoEle.setAttribute(XmlUtils.HR_ATTR_ADDRESS, mAddress);
+        }
+        if (mCountryRegion != null) {
+            developerInfoEle.setAttribute(XmlUtils.HR_ATTR_COUNTRY_REGION, mCountryRegion);
+        }
+        if (mDeveloperRelationship != null) {
+            developerInfoEle.setAttribute(
+                    XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, mDeveloperRelationship.toString());
+        }
+        if (mWebsite != null) {
+            developerInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, mWebsite);
+        }
+        if (mAppDeveloperRegistryId != null) {
+            developerInfoEle.setAttribute(
+                    XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, mAppDeveloperRegistryId);
+        }
+        return developerInfoEle;
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
new file mode 100644
index 0000000..e82a53a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.asllib.marshallable;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Element;
+
+public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInfo> {
+    /** Creates a {@link DeveloperInfo} from the human-readable DOM element. */
+    public DeveloperInfo createFromHrElement(Element developerInfoEle)
+            throws MalformedXmlException {
+        if (developerInfoEle == null) {
+            AslgenUtil.logI("No DeveloperInfo found in hr format.");
+            return null;
+        }
+        String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME, true);
+        String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL, true);
+        String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS, true);
+        String countryRegion =
+                XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION, true);
+        DeveloperInfo.DeveloperRelationship developerRelationship =
+                DeveloperInfo.DeveloperRelationship.forString(
+                        XmlUtils.getStringAttr(
+                                developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, true));
+        String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+        String appDeveloperRegistryId =
+                XmlUtils.getStringAttr(
+                        developerInfoEle, XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, false);
+        return new DeveloperInfo(
+                name,
+                email,
+                address,
+                countryRegion,
+                developerRelationship,
+                website,
+                appDeveloperRegistryId);
+    }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    public DeveloperInfo createFromOdElement(Element developerInfoEle)
+            throws MalformedXmlException {
+        if (developerInfoEle == null) {
+            AslgenUtil.logI("No DeveloperInfo found in od format.");
+            return null;
+        }
+        String name = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_NAME, true);
+        String email = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_EMAIL, true);
+        String address = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_ADDRESS, true);
+        String countryRegion =
+                XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_COUNTRY_REGION, true);
+        DeveloperInfo.DeveloperRelationship developerRelationship =
+                DeveloperInfo.DeveloperRelationship.forValue(
+                        (int)
+                                (long)
+                                        XmlUtils.getOdLongEle(
+                                                developerInfoEle,
+                                                XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+                                                true));
+        String website = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_WEBSITE, false);
+        String appDeveloperRegistryId =
+                XmlUtils.getOdStringEle(
+                        developerInfoEle, XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID, false);
+        return new DeveloperInfo(
+                name,
+                email,
+                address,
+                countryRegion,
+                developerRelationship,
+                website,
+                appDeveloperRegistryId);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 2a4e130..1a83c02 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -21,40 +21,50 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 /** Safety Label representation containing zero or more {@link DataCategory} for data shared */
 public class SafetyLabels implements AslMarshallable {
     private final DataLabels mDataLabels;
+    private final SecurityLabels mSecurityLabels;
+    private final ThirdPartyVerification mThirdPartyVerification;
 
-    public SafetyLabels(DataLabels dataLabels) {
+    public SafetyLabels(
+            DataLabels dataLabels,
+            SecurityLabels securityLabels,
+            ThirdPartyVerification thirdPartyVerification) {
         this.mDataLabels = dataLabels;
-    }
-
-    /** Returns the data label for the safety label */
-    public DataLabels getDataLabel() {
-        return mDataLabels;
+        this.mSecurityLabels = securityLabels;
+        this.mThirdPartyVerification = thirdPartyVerification;
     }
 
     /** Creates an on-device DOM element from the {@link SafetyLabels}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element safetyLabelsEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
         if (mDataLabels != null) {
-            XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
+            safetyLabelsEle.appendChild(mDataLabels.toOdDomElement(doc));
         }
-        return XmlUtils.listOf(safetyLabelsEle);
+        if (mSecurityLabels != null) {
+            safetyLabelsEle.appendChild(mSecurityLabels.toOdDomElement(doc));
+        }
+        if (mThirdPartyVerification != null) {
+            safetyLabelsEle.appendChild(mThirdPartyVerification.toOdDomElement(doc));
+        }
+        return safetyLabelsEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element safetyLabelsEle = doc.createElement(XmlUtils.HR_TAG_SAFETY_LABELS);
 
         if (mDataLabels != null) {
-            XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toHrDomElements(doc));
+            safetyLabelsEle.appendChild(mDataLabels.toHrDomElement(doc));
         }
-        return XmlUtils.listOf(safetyLabelsEle);
+        if (mSecurityLabels != null) {
+            safetyLabelsEle.appendChild(mSecurityLabels.toHrDomElement(doc));
+        }
+        if (mThirdPartyVerification != null) {
+            safetyLabelsEle.appendChild(mThirdPartyVerification.toHrDomElement(doc));
+        }
+        return safetyLabelsEle;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 2738337..35804d9 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -16,54 +16,109 @@
 
 package com.android.asllib.marshallable;
 
-import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Element;
 
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> {
+    private final Map<Long, Set<String>> mRecognizedHrAttrs =
+            Map.ofEntries(Map.entry(1L, Set.of()));
+    private final Map<Long, Set<String>> mRequiredHrAttrs = Map.ofEntries(Map.entry(1L, Set.of()));
+    private final Map<Long, Set<String>> mRecognizedHrEles =
+            Map.ofEntries(
+                    Map.entry(
+                            1L,
+                            Set.of(
+                                    XmlUtils.HR_TAG_DATA_LABELS,
+                                    XmlUtils.HR_TAG_SECURITY_LABELS,
+                                    XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION)),
+                    Map.entry(2L, Set.of(XmlUtils.HR_TAG_DATA_LABELS)));
+    private final Map<Long, Set<String>> mRequiredHrEles = Map.ofEntries(Map.entry(1L, Set.of()));
+    private final Map<Long, Set<String>> mRecognizedOdEleNames =
+            Map.ofEntries(
+                    Map.entry(
+                            1L,
+                            Set.of(
+                                    XmlUtils.OD_NAME_DATA_LABELS,
+                                    XmlUtils.OD_NAME_SECURITY_LABELS,
+                                    XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION)),
+                    Map.entry(2L, Set.of(XmlUtils.OD_NAME_DATA_LABELS)));
+    private final Map<Long, Set<String>> mRequiredOdEles = Map.ofEntries(Map.entry(1L, Set.of()));
 
     /** Creates a {@link SafetyLabels} from the human-readable DOM element. */
-    @Override
-    public SafetyLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
-        Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
+    public SafetyLabels createFromHrElement(Element safetyLabelsEle, long version)
+            throws MalformedXmlException {
         if (safetyLabelsEle == null) {
-            AslgenUtil.logI("No SafetyLabels found in hr format.");
             return null;
         }
 
+        XmlUtils.throwIfExtraneousAttributes(
+                safetyLabelsEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+        XmlUtils.throwIfExtraneousChildrenHr(
+                safetyLabelsEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
+
+        var requiredHrEles = XmlUtils.getMostRecentVersion(mRequiredHrEles, version);
+
         DataLabels dataLabels =
                 new DataLabelsFactory()
-                        .createFromHrElements(
-                                XmlUtils.listOf(
-                                        XmlUtils.getSingleChildElement(
-                                                safetyLabelsEle,
-                                                XmlUtils.HR_TAG_DATA_LABELS,
-                                                false)));
-        return new SafetyLabels(dataLabels);
+                        .createFromHrElement(
+                                XmlUtils.getSingleChildElement(
+                                        safetyLabelsEle,
+                                        XmlUtils.HR_TAG_DATA_LABELS,
+                                        requiredHrEles));
+        SecurityLabels securityLabels =
+                new SecurityLabelsFactory()
+                        .createFromHrElement(
+                                XmlUtils.getSingleChildElement(
+                                        safetyLabelsEle,
+                                        XmlUtils.HR_TAG_SECURITY_LABELS,
+                                        requiredHrEles));
+        ThirdPartyVerification thirdPartyVerification =
+                new ThirdPartyVerificationFactory()
+                        .createFromHrElement(
+                                XmlUtils.getSingleChildElement(
+                                        safetyLabelsEle,
+                                        XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION,
+                                        requiredHrEles));
+        return new SafetyLabels(dataLabels, securityLabels, thirdPartyVerification);
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public SafetyLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
-        Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
+    public SafetyLabels createFromOdElement(Element safetyLabelsEle, long version)
+            throws MalformedXmlException {
         if (safetyLabelsEle == null) {
-            AslgenUtil.logI("No SafetyLabels found in od format.");
             return null;
         }
 
+        XmlUtils.throwIfExtraneousChildrenOd(
+                safetyLabelsEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+        var requiredOdEles = XmlUtils.getMostRecentVersion(mRequiredOdEles, version);
+
         DataLabels dataLabels =
                 new DataLabelsFactory()
-                        .createFromOdElements(
-                                XmlUtils.listOf(
-                                        XmlUtils.getOdPbundleWithName(
-                                                safetyLabelsEle,
-                                                XmlUtils.OD_NAME_DATA_LABELS,
-                                                false)));
-
-        return new SafetyLabels(dataLabels);
+                        .createFromOdElement(
+                                XmlUtils.getOdPbundleWithName(
+                                        safetyLabelsEle,
+                                        XmlUtils.OD_NAME_DATA_LABELS,
+                                        requiredOdEles));
+        SecurityLabels securityLabels =
+                new SecurityLabelsFactory()
+                        .createFromOdElement(
+                                XmlUtils.getOdPbundleWithName(
+                                        safetyLabelsEle,
+                                        XmlUtils.OD_NAME_SECURITY_LABELS,
+                                        requiredOdEles));
+        ThirdPartyVerification thirdPartyVerification =
+                new ThirdPartyVerificationFactory()
+                        .createFromOdElement(
+                                XmlUtils.getOdPbundleWithName(
+                                        safetyLabelsEle,
+                                        XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION,
+                                        requiredOdEles));
+        return new SafetyLabels(dataLabels, securityLabels, thirdPartyVerification);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
index 48643ba..ccb8445 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
@@ -21,8 +21,6 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 /** Security Labels representation */
 public class SecurityLabels implements AslMarshallable {
 
@@ -35,8 +33,7 @@
     }
 
     /** Creates an on-device DOM element from the {@link SecurityLabels}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element ele = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SECURITY_LABELS);
         if (mIsDataDeletable != null) {
             ele.appendChild(
@@ -48,12 +45,11 @@
                     XmlUtils.createOdBooleanEle(
                             doc, XmlUtils.OD_NAME_IS_DATA_ENCRYPTED, mIsDataEncrypted));
         }
-        return XmlUtils.listOf(ele);
+        return ele;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element ele = doc.createElement(XmlUtils.HR_TAG_SECURITY_LABELS);
         if (mIsDataDeletable != null) {
             ele.setAttribute(XmlUtils.HR_ATTR_IS_DATA_DELETABLE, String.valueOf(mIsDataDeletable));
@@ -61,6 +57,6 @@
         if (mIsDataEncrypted != null) {
             ele.setAttribute(XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, String.valueOf(mIsDataEncrypted));
         }
-        return XmlUtils.listOf(ele);
+        return ele;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
index 525a803..da98a42 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
@@ -22,15 +22,10 @@
 
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLabels> {
 
     /** Creates a {@link SecurityLabels} from the human-readable DOM element. */
-    @Override
-    public SecurityLabels createFromHrElements(List<Element> elements)
-            throws MalformedXmlException {
-        Element ele = XmlUtils.getSingleElement(elements);
+    public SecurityLabels createFromHrElement(Element ele) throws MalformedXmlException {
         if (ele == null) {
             AslgenUtil.logI("No SecurityLabels found in hr format.");
             return null;
@@ -43,10 +38,7 @@
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public SecurityLabels createFromOdElements(List<Element> elements)
-            throws MalformedXmlException {
-        Element ele = XmlUtils.getSingleElement(elements);
+    public SecurityLabels createFromOdElement(Element ele) throws MalformedXmlException {
         if (ele == null) {
             AslgenUtil.logI("No SecurityLabels found in od format.");
             return null;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 242e7be..10d6e1a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -21,34 +21,43 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 /** Safety Label representation containing zero or more {@link DataCategory} for data shared */
 public class SystemAppSafetyLabel implements AslMarshallable {
 
+    private final String mUrl;
     private final Boolean mDeclaration;
 
-    public SystemAppSafetyLabel(Boolean d) {
+    public SystemAppSafetyLabel(String url, Boolean d) {
         this.mDeclaration = d;
+        this.mUrl = null;
     }
 
     /** Creates an on-device DOM element from the {@link SystemAppSafetyLabel}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element systemAppSafetyLabelEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL);
-        systemAppSafetyLabelEle.appendChild(
-                XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_DECLARATION, mDeclaration));
-        return XmlUtils.listOf(systemAppSafetyLabelEle);
+        if (mUrl != null) {
+            systemAppSafetyLabelEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+        }
+        if (mDeclaration != null) {
+            systemAppSafetyLabelEle.appendChild(
+                    XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_DECLARATION, mDeclaration));
+        }
+        return systemAppSafetyLabelEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element systemAppSafetyLabelEle =
                 doc.createElement(XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL);
-        XmlUtils.maybeSetHrBoolAttr(
-                systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, mDeclaration);
-        return XmlUtils.listOf(systemAppSafetyLabelEle);
+        if (mUrl != null) {
+            systemAppSafetyLabelEle.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
+        }
+        if (mDeclaration != null) {
+            systemAppSafetyLabelEle.setAttribute(
+                    XmlUtils.HR_ATTR_DECLARATION, String.valueOf(mDeclaration));
+        }
+        return systemAppSafetyLabelEle;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index 7f4aa7a..971ae92 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -16,42 +16,77 @@
 
 package com.android.asllib.marshallable;
 
-import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Element;
 
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<SystemAppSafetyLabel> {
+    private final Map<Long, Set<String>> mRecognizedHrAttrs =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of(XmlUtils.HR_ATTR_URL)),
+                    Map.entry(2L, Set.of(XmlUtils.HR_ATTR_DECLARATION)));
+    private final Map<Long, Set<String>> mRequiredHrAttrs =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of(XmlUtils.HR_ATTR_URL)),
+                    Map.entry(2L, Set.of(XmlUtils.HR_ATTR_DECLARATION)));
+    private final Map<Long, Set<String>> mRecognizedHrEles = Map.ofEntries(Map.entry(1L, Set.of()));
+    private final Map<Long, Set<String>> mRecognizedOdEleNames =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of(XmlUtils.OD_NAME_URL)),
+                    Map.entry(2L, Set.of(XmlUtils.OD_NAME_DECLARATION)));
+    private final Map<Long, Set<String>> mRequiredOdEleNames =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of(XmlUtils.OD_NAME_URL)),
+                    Map.entry(2L, Set.of(XmlUtils.OD_NAME_DECLARATION)));
 
     /** Creates a {@link SystemAppSafetyLabel} from the human-readable DOM element. */
-    @Override
-    public SystemAppSafetyLabel createFromHrElements(List<Element> elements)
+    public SystemAppSafetyLabel createFromHrElement(Element systemAppSafetyLabelEle, long version)
             throws MalformedXmlException {
-        Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
         if (systemAppSafetyLabelEle == null) {
-            AslgenUtil.logI("No SystemAppSafetyLabel found in hr format.");
             return null;
         }
+        XmlUtils.throwIfExtraneousAttributes(
+                systemAppSafetyLabelEle,
+                XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+        XmlUtils.throwIfExtraneousChildrenHr(
+                systemAppSafetyLabelEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
 
+        String url =
+                XmlUtils.getStringAttr(
+                        systemAppSafetyLabelEle,
+                        XmlUtils.HR_ATTR_URL,
+                        XmlUtils.getMostRecentVersion(mRequiredHrAttrs, version));
         Boolean declaration =
-                XmlUtils.getBoolAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, true);
-        return new SystemAppSafetyLabel(declaration);
+                XmlUtils.getBoolAttr(
+                        systemAppSafetyLabelEle,
+                        XmlUtils.HR_ATTR_DECLARATION,
+                        XmlUtils.getMostRecentVersion(mRequiredHrAttrs, version));
+        return new SystemAppSafetyLabel(url, declaration);
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public SystemAppSafetyLabel createFromOdElements(List<Element> elements)
+    public SystemAppSafetyLabel createFromOdElement(Element systemAppSafetyLabelEle, long version)
             throws MalformedXmlException {
-        Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
         if (systemAppSafetyLabelEle == null) {
-            AslgenUtil.logI("No SystemAppSafetyLabel found in od format.");
             return null;
         }
+        XmlUtils.throwIfExtraneousChildrenOd(
+                systemAppSafetyLabelEle,
+                XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+        String url =
+                XmlUtils.getOdStringEle(
+                        systemAppSafetyLabelEle,
+                        XmlUtils.OD_NAME_URL,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
         Boolean declaration =
-                XmlUtils.getOdBoolEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_DECLARATION, true);
-        return new SystemAppSafetyLabel(declaration);
+                XmlUtils.getOdBoolEle(
+                        systemAppSafetyLabelEle,
+                        XmlUtils.OD_NAME_DECLARATION,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
+        return new SystemAppSafetyLabel(url, declaration);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
index d74f3f0..151cdb1 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
@@ -21,8 +21,6 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 /** ThirdPartyVerification representation. */
 public class ThirdPartyVerification implements AslMarshallable {
 
@@ -33,19 +31,17 @@
     }
 
     /** Creates an on-device DOM element from the {@link ThirdPartyVerification}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element ele =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION);
         ele.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
-        return XmlUtils.listOf(ele);
+        return ele;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element ele = doc.createElement(XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION);
         ele.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
-        return XmlUtils.listOf(ele);
+        return ele;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
index 197e7aa..f229ad6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
@@ -22,16 +22,11 @@
 
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 public class ThirdPartyVerificationFactory
         implements AslMarshallableFactory<ThirdPartyVerification> {
 
     /** Creates a {@link ThirdPartyVerification} from the human-readable DOM element. */
-    @Override
-    public ThirdPartyVerification createFromHrElements(List<Element> elements)
-            throws MalformedXmlException {
-        Element ele = XmlUtils.getSingleElement(elements);
+    public ThirdPartyVerification createFromHrElement(Element ele) throws MalformedXmlException {
         if (ele == null) {
             AslgenUtil.logI("No ThirdPartyVerification found in hr format.");
             return null;
@@ -42,10 +37,7 @@
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public ThirdPartyVerification createFromOdElements(List<Element> elements)
-            throws MalformedXmlException {
-        Element ele = XmlUtils.getSingleElement(elements);
+    public ThirdPartyVerification createFromOdElement(Element ele) throws MalformedXmlException {
         if (ele == null) {
             AslgenUtil.logI("No ThirdPartyVerification found in od format.");
             return null;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index 9f789f0..f24e6bf 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -21,39 +21,39 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.util.List;
-
 /** TransparencyInfo representation containing {@link AppInfo} */
 public class TransparencyInfo implements AslMarshallable {
+
+    private final DeveloperInfo mDeveloperInfo;
     private final AppInfo mAppInfo;
 
-    public TransparencyInfo(AppInfo appInfo) {
+    public TransparencyInfo(DeveloperInfo developerInfo, AppInfo appInfo) {
+        this.mDeveloperInfo = developerInfo;
         this.mAppInfo = appInfo;
     }
 
-    /** Gets the {@link AppInfo} of the {@link TransparencyInfo}. */
-    public AppInfo getAppInfo() {
-        return mAppInfo;
-    }
-
     /** Creates an on-device DOM element from the {@link TransparencyInfo}. */
-    @Override
-    public List<Element> toOdDomElements(Document doc) {
+    public Element toOdDomElement(Document doc) {
         Element transparencyInfoEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_TRANSPARENCY_INFO);
-        if (mAppInfo != null) {
-            XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toOdDomElements(doc));
+        if (mDeveloperInfo != null) {
+            transparencyInfoEle.appendChild(mDeveloperInfo.toOdDomElement(doc));
         }
-        return XmlUtils.listOf(transparencyInfoEle);
+        if (mAppInfo != null) {
+            transparencyInfoEle.appendChild(mAppInfo.toOdDomElement(doc));
+        }
+        return transparencyInfoEle;
     }
 
     /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
-    @Override
-    public List<Element> toHrDomElements(Document doc) {
+    public Element toHrDomElement(Document doc) {
         Element transparencyInfoEle = doc.createElement(XmlUtils.HR_TAG_TRANSPARENCY_INFO);
-        if (mAppInfo != null) {
-            XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toHrDomElements(doc));
+        if (mDeveloperInfo != null) {
+            transparencyInfoEle.appendChild(mDeveloperInfo.toHrDomElement(doc));
         }
-        return XmlUtils.listOf(transparencyInfoEle);
+        if (mAppInfo != null) {
+            transparencyInfoEle.appendChild(mAppInfo.toHrDomElement(doc));
+        }
+        return transparencyInfoEle;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index 40f2872..9e98941 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -16,47 +16,84 @@
 
 package com.android.asllib.marshallable;
 
-import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Element;
 
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class TransparencyInfoFactory implements AslMarshallableFactory<TransparencyInfo> {
+    private final Map<Long, Set<String>> mRecognizedHrAttrs =
+            Map.ofEntries(Map.entry(1L, Set.of()));
+    private final Map<Long, Set<String>> mRequiredHrAttrs = Map.ofEntries(Map.entry(1L, Set.of()));
+    private final Map<Long, Set<String>> mRecognizedHrEles =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of(XmlUtils.HR_TAG_DEVELOPER_INFO, XmlUtils.HR_TAG_APP_INFO)),
+                    Map.entry(2L, Set.of(XmlUtils.HR_TAG_APP_INFO)));
+    private final Map<Long, Set<String>> mRequiredHrEles =
+            Map.ofEntries(Map.entry(1L, Set.of()), Map.entry(2L, Set.of(XmlUtils.HR_TAG_APP_INFO)));
+    private final Map<Long, Set<String>> mRecognizedOdEleNames =
+            Map.ofEntries(
+                    Map.entry(
+                            1L, Set.of(XmlUtils.OD_NAME_DEVELOPER_INFO, XmlUtils.OD_NAME_APP_INFO)),
+                    Map.entry(2L, Set.of(XmlUtils.OD_NAME_APP_INFO)));
+    private final Map<Long, Set<String>> mRequiredOdEles =
+            Map.ofEntries(
+                    Map.entry(1L, Set.of()), Map.entry(2L, Set.of(XmlUtils.OD_NAME_APP_INFO)));
 
     /** Creates a {@link TransparencyInfo} from the human-readable DOM element. */
-    @Override
-    public TransparencyInfo createFromHrElements(List<Element> elements)
+    public TransparencyInfo createFromHrElement(Element transparencyInfoEle, long version)
             throws MalformedXmlException {
-        Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
         if (transparencyInfoEle == null) {
-            AslgenUtil.logI("No TransparencyInfo found in hr format.");
             return null;
         }
+        XmlUtils.throwIfExtraneousAttributes(
+                transparencyInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+        XmlUtils.throwIfExtraneousChildrenHr(
+                transparencyInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
 
+        Element developerInfoEle =
+                XmlUtils.getSingleChildElement(
+                        transparencyInfoEle,
+                        XmlUtils.HR_TAG_DEVELOPER_INFO,
+                        XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
+        DeveloperInfo developerInfo =
+                new DeveloperInfoFactory().createFromHrElement(developerInfoEle);
         Element appInfoEle =
-                XmlUtils.getSingleChildElement(transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, true);
-        AppInfo appInfo = new AppInfoFactory().createFromHrElements(XmlUtils.listOf(appInfoEle));
+                XmlUtils.getSingleChildElement(
+                        transparencyInfoEle,
+                        XmlUtils.HR_TAG_APP_INFO,
+                        XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
+        AppInfo appInfo = new AppInfoFactory().createFromHrElement(appInfoEle, version);
 
-        return new TransparencyInfo(appInfo);
+        return new TransparencyInfo(developerInfo, appInfo);
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
-    public TransparencyInfo createFromOdElements(List<Element> elements)
+    public TransparencyInfo createFromOdElement(Element transparencyInfoEle, long version)
             throws MalformedXmlException {
-        Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
         if (transparencyInfoEle == null) {
-            AslgenUtil.logI("No TransparencyInfo found in od format.");
             return null;
         }
+        XmlUtils.throwIfExtraneousChildrenOd(
+                transparencyInfoEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
 
+        Element developerInfoEle =
+                XmlUtils.getOdPbundleWithName(
+                        transparencyInfoEle,
+                        XmlUtils.OD_NAME_DEVELOPER_INFO,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEles, version));
+        DeveloperInfo developerInfo =
+                new DeveloperInfoFactory().createFromOdElement(developerInfoEle);
         Element appInfoEle =
-                XmlUtils.getOdPbundleWithName(transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, true);
-        AppInfo appInfo = new AppInfoFactory().createFromOdElements(XmlUtils.listOf(appInfoEle));
+                XmlUtils.getOdPbundleWithName(
+                        transparencyInfoEle,
+                        XmlUtils.OD_NAME_APP_INFO,
+                        XmlUtils.getMostRecentVersion(mRequiredOdEles, version));
+        AppInfo appInfo = new AppInfoFactory().createFromOdElement(appInfoEle, version);
 
-        return new TransparencyInfo(appInfo);
+        return new TransparencyInfo(developerInfo, appInfo);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 2c1517b..52c4390 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -25,6 +25,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 public class XmlUtils {
@@ -70,6 +72,8 @@
     public static final String HR_ATTR_ADS_FINGERPRINTING = "adsFingerprinting";
     public static final String HR_ATTR_SECURITY_FINGERPRINTING = "securityFingerprinting";
     public static final String HR_ATTR_PRIVACY_POLICY = "privacyPolicy";
+    public static final String HR_ATTR_DEVELOPER_ID = "developerId";
+    public static final String HR_ATTR_APPLICATION_ID = "applicationId";
     public static final String HR_ATTR_SECURITY_ENDPOINTS = "securityEndpoints";
     public static final String HR_TAG_FIRST_PARTY_ENDPOINTS = "first-party-endpoints";
     public static final String HR_TAG_SERVICE_PROVIDER_ENDPOINTS = "service-provider-endpoints";
@@ -102,10 +106,12 @@
     public static final String OD_NAME_CONTAINS_ADS = "contains_ads";
     public static final String OD_NAME_OBEY_APS = "obey_aps";
     public static final String OD_NAME_APS_COMPLIANT = "aps_compliant";
+    public static final String OD_NAME_DEVELOPER_ID = "developer_id";
+    public static final String OD_NAME_APPLICATION_ID = "application_id";
     public static final String OD_NAME_ADS_FINGERPRINTING = "ads_fingerprinting";
     public static final String OD_NAME_SECURITY_FINGERPRINTING = "security_fingerprinting";
     public static final String OD_NAME_PRIVACY_POLICY = "privacy_policy";
-    public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoints";
+    public static final String OD_NAME_SECURITY_ENDPOINTS = "security_endpoints";
     public static final String OD_NAME_FIRST_PARTY_ENDPOINTS = "first_party_endpoints";
     public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINTS = "service_provider_endpoints";
     public static final String OD_NAME_CATEGORY = "category";
@@ -140,6 +146,15 @@
     /**
      * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
      */
+    public static Element getSingleChildElement(
+            Node parentEle, String tagName, Set<String> requiredStrings)
+            throws MalformedXmlException {
+        return getSingleChildElement(parentEle, tagName, requiredStrings.contains(tagName));
+    }
+
+    /**
+     * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+     */
     public static Element getSingleChildElement(Node parentEle, String tagName, boolean required)
             throws MalformedXmlException {
         String parentTagNameForErrorMsg =
@@ -287,19 +302,36 @@
     }
 
     /** Gets a pipeline-split attribute. */
+    public static List<String> getPipelineSplitAttr(
+            Element ele, String attrName, Set<String> requiredNames) throws MalformedXmlException {
+        return getPipelineSplitAttr(ele, attrName, requiredNames.contains(attrName));
+    }
+
+    /** Gets a pipeline-split attribute. */
     public static List<String> getPipelineSplitAttr(Element ele, String attrName, boolean required)
             throws MalformedXmlException {
         List<String> list =
                 Arrays.stream(ele.getAttribute(attrName).split("\\|")).collect(Collectors.toList());
-        if ((list.isEmpty() || list.get(0).isEmpty()) && required) {
-            throw new MalformedXmlException(
-                    String.format(
-                            "Delimited string %s was required but missing, in %s.",
-                            attrName, ele.getTagName()));
+        if ((list.isEmpty() || list.get(0).isEmpty())) {
+            if (required) {
+                throw new MalformedXmlException(
+                        String.format(
+                                "Delimited string %s was required but missing, in %s.",
+                                attrName, ele.getTagName()));
+            }
+            return null;
         }
         return list;
     }
 
+    /**
+     * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+     */
+    public static Boolean getBoolAttr(Element ele, String attrName, Set<String> requiredStrings)
+            throws MalformedXmlException {
+        return getBoolAttr(ele, attrName, requiredStrings.contains(attrName));
+    }
+
     /** Gets a Boolean attribute. */
     public static Boolean getBoolAttr(Element ele, String attrName, boolean required)
             throws MalformedXmlException {
@@ -314,6 +346,12 @@
     }
 
     /** Gets a Boolean attribute. */
+    public static Boolean getOdBoolEle(Element ele, String nameName, Set<String> requiredNames)
+            throws MalformedXmlException {
+        return getOdBoolEle(ele, nameName, requiredNames.contains(nameName));
+    }
+
+    /** Gets a Boolean attribute. */
     public static Boolean getOdBoolEle(Element ele, String nameName, boolean required)
             throws MalformedXmlException {
         List<Element> boolEles =
@@ -376,6 +414,12 @@
     }
 
     /** Gets an on-device String attribute. */
+    public static String getOdStringEle(Element ele, String nameName, Set<String> requiredNames)
+            throws MalformedXmlException {
+        return getOdStringEle(ele, nameName, requiredNames.contains(nameName));
+    }
+
+    /** Gets an on-device String attribute. */
     public static String getOdStringEle(Element ele, String nameName, boolean required)
             throws MalformedXmlException {
         List<Element> eles =
@@ -404,6 +448,13 @@
     }
 
     /** Gets a OD Pbundle Element attribute with the specified name. */
+    public static Element getOdPbundleWithName(
+            Element ele, String nameName, Set<String> requiredStrings)
+            throws MalformedXmlException {
+        return getOdPbundleWithName(ele, nameName, requiredStrings.contains(nameName));
+    }
+
+    /** Gets a OD Pbundle Element attribute with the specified name. */
     public static Element getOdPbundleWithName(Element ele, String nameName, boolean required)
             throws MalformedXmlException {
         List<Element> eles =
@@ -425,6 +476,14 @@
         return eles.get(0);
     }
 
+    /**
+     * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+     */
+    public static String getStringAttr(Element ele, String attrName, Set<String> requiredStrings)
+            throws MalformedXmlException {
+        return getStringAttr(ele, attrName, requiredStrings.contains(attrName));
+    }
+
     /** Gets a required String attribute. */
     public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException {
         return getStringAttr(ele, attrName, true);
@@ -476,6 +535,13 @@
 
     /** Gets human-readable style String array. */
     public static List<String> getHrItemsAsStrings(
+            Element parent, String elementName, Set<String> requiredNames)
+            throws MalformedXmlException {
+        return getHrItemsAsStrings(parent, elementName, requiredNames.contains(elementName));
+    }
+
+    /** Gets human-readable style String array. */
+    public static List<String> getHrItemsAsStrings(
             Element parent, String elementName, boolean required) throws MalformedXmlException {
 
         List<Element> arrayEles = XmlUtils.getChildrenByTagName(parent, elementName);
@@ -501,6 +567,12 @@
     }
 
     /** Gets on-device style String array. */
+    public static List<String> getOdStringArray(
+            Element ele, String nameName, Set<String> requiredNames) throws MalformedXmlException {
+        return getOdStringArray(ele, nameName, requiredNames.contains(nameName));
+    }
+
+    /** Gets on-device style String array. */
     public static List<String> getOdStringArray(Element ele, String nameName, boolean required)
             throws MalformedXmlException {
         List<Element> arrayEles =
@@ -530,6 +602,73 @@
         return strs;
     }
 
+    /** Throws if extraneous child elements detected */
+    public static void throwIfExtraneousChildrenHr(Element ele, Set<String> expectedChildNames)
+            throws MalformedXmlException {
+        var childEles = XmlUtils.asElementList(ele.getChildNodes());
+        List<Element> extraneousEles =
+                childEles.stream()
+                        .filter(e -> !expectedChildNames.contains(e.getTagName()))
+                        .collect(Collectors.toList());
+        if (!extraneousEles.isEmpty()) {
+            throw new MalformedXmlException(
+                    String.format(
+                            "Unexpected element(s) %s in %s.",
+                            extraneousEles.stream()
+                                    .map(Element::getTagName)
+                                    .collect(Collectors.joining(",")),
+                            ele.getTagName()));
+        }
+    }
+
+    /** Throws if extraneous child elements detected */
+    public static void throwIfExtraneousChildrenOd(Element ele, Set<String> expectedChildNames)
+            throws MalformedXmlException {
+        var allChildElements = XmlUtils.asElementList(ele.getChildNodes());
+        List<Element> extraneousEles =
+                allChildElements.stream()
+                        .filter(
+                                e ->
+                                        !e.getAttribute(XmlUtils.OD_ATTR_NAME).isEmpty()
+                                                && !expectedChildNames.contains(
+                                                        e.getAttribute(XmlUtils.OD_ATTR_NAME)))
+                        .collect(Collectors.toList());
+        if (!extraneousEles.isEmpty()) {
+            throw new MalformedXmlException(
+                    String.format(
+                            "Unexpected element(s) in %s: %s",
+                            ele.getTagName(),
+                            extraneousEles.stream()
+                                    .map(
+                                            e ->
+                                                    String.format(
+                                                            "%s name=%s",
+                                                            e.getTagName(),
+                                                            e.getAttribute(XmlUtils.OD_ATTR_NAME)))
+                                    .collect(Collectors.joining(","))));
+        }
+    }
+
+    /** Throws if extraneous attributes detected */
+    public static void throwIfExtraneousAttributes(Element ele, Set<String> expectedAttrNames)
+            throws MalformedXmlException {
+        var attrs = ele.getAttributes();
+        List<String> attrNames = new ArrayList<>();
+        for (int i = 0; i < attrs.getLength(); i++) {
+            attrNames.add(attrs.item(i).getNodeName());
+        }
+        List<String> extraneousAttrs =
+                attrNames.stream()
+                        .filter(s -> !expectedAttrNames.contains(s))
+                        .collect(Collectors.toList());
+        if (!extraneousAttrs.isEmpty()) {
+            throw new MalformedXmlException(
+                    String.format(
+                            "Unexpected attr(s) %s in %s.",
+                            String.join(",", extraneousAttrs), ele.getTagName()));
+        }
+    }
+
     /**
      * Utility method for making a List from one element, to support easier refactoring if needed.
      * For example, List.of() doesn't support null elements.
@@ -537,4 +676,26 @@
     public static List<Element> listOf(Element e) {
         return Arrays.asList(e);
     }
+
+    /**
+     * Gets the most recent version of fields in the mapping. This way when a new version is
+     * released, we only need to update the mappings that were modified. The rest will fall back to
+     * the most recent previous version.
+     */
+    public static Set<String> getMostRecentVersion(
+            Map<Long, Set<String>> versionToFieldsMapping, long version)
+            throws MalformedXmlException {
+        long bestVersion = 0;
+        Set<String> bestSet = null;
+        for (Map.Entry<Long, Set<String>> entry : versionToFieldsMapping.entrySet()) {
+            if (entry.getKey() > bestVersion && entry.getKey() <= version) {
+                bestVersion = entry.getKey();
+                bestSet = entry.getValue();
+            }
+        }
+        if (bestSet == null) {
+            throw new MalformedXmlException("Unexpected version: " + version);
+        }
+        return bestSet;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index 5d1d45a..262f76d 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -41,6 +41,18 @@
     /** Logic for setting up tests (empty if not yet needed). */
     public static void main(String[] params) throws Exception {}
 
+    @Test
+    public void testValidOd() throws Exception {
+        System.out.println("start testing valid od.");
+        Path odPath = Paths.get(VALID_MAPPINGS_PATH, "general-v1", OD_XML_FILENAME);
+        InputStream odStream = getClass().getClassLoader().getResourceAsStream(odPath.toString());
+        String odContents =
+                TestUtils.getFormattedXml(
+                        new String(odStream.readAllBytes(), StandardCharsets.UTF_8), false);
+        AndroidSafetyLabel unusedAsl =
+                AslConverter.readFromString(odContents, AslConverter.Format.ON_DEVICE);
+    }
+
     /** Tests valid mappings between HR and OD. */
     @Test
     public void testValidMappings() throws Exception {
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
index 61a7823..283ccbc 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
@@ -16,12 +16,18 @@
 
 package com.android.asllib.marshallable;
 
+import static org.junit.Assert.assertThrows;
+
 import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+
+import java.nio.file.Paths;
 
 @RunWith(JUnit4.class)
 public class AndroidSafetyLabelTest {
@@ -66,47 +72,49 @@
         testOdToHrAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME);
     }
 
-    /** Test for android safety label with system app safety label. */
-    @Test
-    public void testAndroidSafetyLabelWithSystemAppSafetyLabel() throws Exception {
-        System.out.println("starting testAndroidSafetyLabelWithSystemAppSafetyLabel.");
-        testHrToOdAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
-        testOdToHrAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
-    }
-
-    /** Test for android safety label with transparency info. */
-    @Test
-    public void testAndroidSafetyLabelWithTransparencyInfo() throws Exception {
-        System.out.println("starting testAndroidSafetyLabelWithTransparencyInfo.");
-        testHrToOdAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
-        testOdToHrAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
-    }
-
     private void hrToOdExpectException(String fileName) {
-        TestUtils.hrToOdExpectException(
-                new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_HR_PATH, fileName);
+        assertThrows(
+                MalformedXmlException.class,
+                () -> {
+                    new AndroidSafetyLabelFactory()
+                            .createFromHrElement(
+                                    TestUtils.getElementFromResource(
+                                            Paths.get(ANDROID_SAFETY_LABEL_HR_PATH, fileName)));
+                });
     }
 
     private void odToHrExpectException(String fileName) {
-        TestUtils.odToHrExpectException(
-                new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_OD_PATH, fileName);
+        assertThrows(
+                MalformedXmlException.class,
+                () -> {
+                    new AndroidSafetyLabelFactory()
+                            .createFromOdElement(
+                                    TestUtils.getElementFromResource(
+                                            Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, fileName)));
+                });
     }
 
     private void testHrToOdAndroidSafetyLabel(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new AndroidSafetyLabelFactory(),
-                ANDROID_SAFETY_LABEL_HR_PATH,
-                ANDROID_SAFETY_LABEL_OD_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        AndroidSafetyLabel asl =
+                new AndroidSafetyLabelFactory()
+                        .createFromHrElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(ANDROID_SAFETY_LABEL_HR_PATH, fileName)));
+        Element aslEle = asl.toOdDomElement(doc);
+        doc.appendChild(aslEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, fileName));
     }
 
     private void testOdToHrAndroidSafetyLabel(String fileName) throws Exception {
-        TestUtils.testOdToHr(
-                TestUtils.document(),
-                new AndroidSafetyLabelFactory(),
-                ANDROID_SAFETY_LABEL_OD_PATH,
-                ANDROID_SAFETY_LABEL_HR_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        AndroidSafetyLabel asl =
+                new AndroidSafetyLabelFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, fileName)));
+        Element aslEle = asl.toHrDomElement(doc);
+        doc.appendChild(aslEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(ANDROID_SAFETY_LABEL_HR_PATH, fileName));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index d823c48..7806061 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -26,12 +26,14 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
 
 import java.nio.file.Paths;
 import java.util.List;
 
 @RunWith(JUnit4.class)
 public class AppInfoTest {
+    private static final long DEFAULT_VERSION = 2L;
     private static final String APP_INFO_HR_PATH = "com/android/asllib/appinfo/hr";
     private static final String APP_INFO_OD_PATH = "com/android/asllib/appinfo/od";
     public static final List<String> REQUIRED_FIELD_NAMES =
@@ -45,7 +47,24 @@
     public static final List<String> OPTIONAL_FIELD_NAMES = List.of();
     public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of();
 
+    public static final List<String> REQUIRED_FIELD_NAMES_OD_V1 =
+            List.of(
+                    "title",
+                    "description",
+                    "contains_ads",
+                    "obey_aps",
+                    "ads_fingerprinting",
+                    "security_fingerprinting",
+                    "privacy_policy",
+                    "security_endpoints",
+                    "first_party_endpoints",
+                    "service_provider_endpoints",
+                    "category");
+    public static final List<String> OPTIONAL_FIELD_NAMES_OD_V1 = List.of("website", "email");
+
     private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+    private static final String ALL_FIELDS_VALID_V1_FILE_NAME = "all-fields-valid-v1.xml";
+    public static final String UNRECOGNIZED_V1_FILE_NAME = "unrecognized-v1.xml";
 
     /** Logic for setting up tests (empty if not yet needed). */
     public static void main(String[] params) throws Exception {}
@@ -63,6 +82,61 @@
         testOdToHrAppInfo(ALL_FIELDS_VALID_FILE_NAME);
     }
 
+    /** Test for all fields valid v1. */
+    @Test
+    public void testAllFieldsValidV1() throws Exception {
+        System.out.println("starting testAllFieldsValidV1.");
+        new AppInfoFactory()
+                .createFromOdElement(
+                        TestUtils.getElementFromResource(
+                                Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
+                        1L);
+    }
+
+    /** Test for unrecognized field v1. */
+    @Test
+    public void testUnrecognizedFieldV1() throws Exception {
+        System.out.println("starting testUnrecognizedFieldV1.");
+        assertThrows(
+                MalformedXmlException.class,
+                () ->
+                        new AppInfoFactory()
+                                .createFromOdElement(
+                                        TestUtils.getElementFromResource(
+                                                Paths.get(
+                                                        APP_INFO_OD_PATH,
+                                                        UNRECOGNIZED_V1_FILE_NAME)),
+                                        1L));
+    }
+
+    /** Tests missing required fields fails, V1. */
+    @Test
+    public void testMissingRequiredFieldsOdV1() throws Exception {
+        for (String reqField : REQUIRED_FIELD_NAMES_OD_V1) {
+            System.out.println("testing missing required field od v1: " + reqField);
+            var appInfoEle =
+                    TestUtils.getElementFromResource(
+                            Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
+            TestUtils.removeOdChildEleWithName(appInfoEle, reqField);
+            assertThrows(
+                    MalformedXmlException.class,
+                    () -> new AppInfoFactory().createFromOdElement(appInfoEle, 1L));
+        }
+    }
+
+    /** Tests missing optional fields passes, V1. */
+    @Test
+    public void testMissingOptionalFieldsOdV1() throws Exception {
+        for (String optField : OPTIONAL_FIELD_NAMES_OD_V1) {
+            System.out.println("testing missing optional field od v1: " + optField);
+            var appInfoEle =
+                    TestUtils.getElementFromResource(
+                            Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
+            TestUtils.removeOdChildEleWithName(appInfoEle, optField);
+            new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
+        }
+    }
+
     /** Tests missing required fields fails. */
     @Test
     public void testMissingRequiredFields() throws Exception {
@@ -70,24 +144,24 @@
         for (String reqField : REQUIRED_FIELD_NAMES) {
             System.out.println("testing missing required field hr: " + reqField);
             var appInfoEle =
-                    TestUtils.getElementsFromResource(
+                    TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
-            appInfoEle.get(0).removeAttribute(reqField);
+            appInfoEle.removeAttribute(reqField);
 
             assertThrows(
                     MalformedXmlException.class,
-                    () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+                    () -> new AppInfoFactory().createFromHrElement(appInfoEle, DEFAULT_VERSION));
         }
 
         for (String reqField : REQUIRED_FIELD_NAMES_OD) {
             System.out.println("testing missing required field od: " + reqField);
             var appInfoEle =
-                    TestUtils.getElementsFromResource(
+                    TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
-            TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField);
+            TestUtils.removeOdChildEleWithName(appInfoEle, reqField);
             assertThrows(
                     MalformedXmlException.class,
-                    () -> new AppInfoFactory().createFromOdElements(appInfoEle));
+                    () -> new AppInfoFactory().createFromOdElement(appInfoEle, DEFAULT_VERSION));
         }
     }
 
@@ -98,24 +172,24 @@
         for (String reqChildName : REQUIRED_CHILD_NAMES) {
             System.out.println("testing missing required child hr: " + reqChildName);
             var appInfoEle =
-                    TestUtils.getElementsFromResource(
+                    TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
-            var child = XmlUtils.getChildrenByTagName(appInfoEle.get(0), reqChildName).get(0);
-            appInfoEle.get(0).removeChild(child);
+            var child = XmlUtils.getChildrenByTagName(appInfoEle, reqChildName).get(0);
+            appInfoEle.removeChild(child);
             assertThrows(
                     MalformedXmlException.class,
-                    () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+                    () -> new AppInfoFactory().createFromHrElement(appInfoEle, DEFAULT_VERSION));
         }
 
         for (String reqField : REQUIRED_CHILD_NAMES_OD) {
             System.out.println("testing missing required child od: " + reqField);
             var appInfoEle =
-                    TestUtils.getElementsFromResource(
+                    TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
-            TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField);
+            TestUtils.removeOdChildEleWithName(appInfoEle, reqField);
             assertThrows(
                     MalformedXmlException.class,
-                    () -> new AppInfoFactory().createFromOdElements(appInfoEle));
+                    () -> new AppInfoFactory().createFromOdElement(appInfoEle, DEFAULT_VERSION));
         }
     }
 
@@ -124,38 +198,51 @@
     public void testMissingOptionalFields() throws Exception {
         for (String optField : OPTIONAL_FIELD_NAMES) {
             var ele =
-                    TestUtils.getElementsFromResource(
+                    TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
-            ele.get(0).removeAttribute(optField);
-            AppInfo appInfo = new AppInfoFactory().createFromHrElements(ele);
-            appInfo.toOdDomElements(TestUtils.document());
+            ele.removeAttribute(optField);
+            AppInfo appInfo = new AppInfoFactory().createFromHrElement(ele, DEFAULT_VERSION);
+            appInfo.toOdDomElement(TestUtils.document());
         }
 
         for (String optField : OPTIONAL_FIELD_NAMES_OD) {
             var ele =
-                    TestUtils.getElementsFromResource(
+                    TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
-            TestUtils.removeOdChildEleWithName(ele.get(0), optField);
-            AppInfo appInfo = new AppInfoFactory().createFromOdElements(ele);
-            appInfo.toHrDomElements(TestUtils.document());
+            TestUtils.removeOdChildEleWithName(ele, optField);
+            AppInfo appInfo = new AppInfoFactory().createFromOdElement(ele, DEFAULT_VERSION);
+            appInfo.toHrDomElement(TestUtils.document());
         }
     }
 
     private void testHrToOdAppInfo(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new AppInfoFactory(),
-                APP_INFO_HR_PATH,
-                APP_INFO_OD_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        AppInfo appInfo =
+                new AppInfoFactory()
+                        .createFromHrElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(APP_INFO_HR_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element appInfoEle = appInfo.toOdDomElement(doc);
+        doc.appendChild(appInfoEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(APP_INFO_OD_PATH, fileName));
     }
 
+
     private void testOdToHrAppInfo(String fileName) throws Exception {
-        TestUtils.testOdToHr(
-                TestUtils.document(),
-                new AppInfoFactory(),
-                APP_INFO_OD_PATH,
-                APP_INFO_HR_PATH,
-                fileName);
+        testOdToHrAppInfo(fileName, DEFAULT_VERSION);
+    }
+
+    private void testOdToHrAppInfo(String fileName, long version) throws Exception {
+        var doc = TestUtils.document();
+        AppInfo appInfo =
+                new AppInfoFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(APP_INFO_OD_PATH, fileName)),
+                                version);
+        Element appInfoEle = appInfo.toHrDomElement(doc);
+        doc.appendChild(appInfoEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(APP_INFO_HR_PATH, fileName));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
index ff43741..b557fea 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -16,15 +16,27 @@
 
 package com.android.asllib.marshallable;
 
+import static org.junit.Assert.assertThrows;
+
 import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.ParserConfigurationException;
 
 @RunWith(JUnit4.class)
 public class DataLabelsTest {
+    private static final long DEFAULT_VERSION = 2L;
+
     private static final String DATA_LABELS_HR_PATH = "com/android/asllib/datalabels/hr";
     private static final String DATA_LABELS_OD_PATH = "com/android/asllib/datalabels/od";
 
@@ -297,29 +309,43 @@
         odToHrExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME);
     }
 
-    private void hrToOdExpectException(String fileName) {
-        TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName);
+    private void hrToOdExpectException(String fileName)
+            throws ParserConfigurationException, IOException, SAXException {
+        var ele = TestUtils.getElementFromResource(Paths.get(DATA_LABELS_HR_PATH, fileName));
+        assertThrows(
+                MalformedXmlException.class,
+                () -> new DataLabelsFactory().createFromHrElement(ele));
     }
 
-    private void odToHrExpectException(String fileName) {
-        TestUtils.odToHrExpectException(new DataLabelsFactory(), DATA_LABELS_OD_PATH, fileName);
+    private void odToHrExpectException(String fileName)
+            throws ParserConfigurationException, IOException, SAXException {
+        var ele = TestUtils.getElementFromResource(Paths.get(DATA_LABELS_OD_PATH, fileName));
+        assertThrows(
+                MalformedXmlException.class,
+                () -> new DataLabelsFactory().createFromOdElement(ele));
     }
 
     private void testHrToOdDataLabels(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new DataLabelsFactory(),
-                DATA_LABELS_HR_PATH,
-                DATA_LABELS_OD_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        DataLabels dataLabels =
+                new DataLabelsFactory()
+                        .createFromHrElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(DATA_LABELS_HR_PATH, fileName)));
+        Element resultingEle = dataLabels.toOdDomElement(doc);
+        doc.appendChild(resultingEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(DATA_LABELS_OD_PATH, fileName));
     }
 
     private void testOdToHrDataLabels(String fileName) throws Exception {
-        TestUtils.testOdToHr(
-                TestUtils.document(),
-                new DataLabelsFactory(),
-                DATA_LABELS_OD_PATH,
-                DATA_LABELS_HR_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        DataLabels dataLabels =
+                new DataLabelsFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(DATA_LABELS_OD_PATH, fileName)));
+        Element resultingEle = dataLabels.toHrDomElement(doc);
+        doc.appendChild(resultingEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(DATA_LABELS_HR_PATH, fileName));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
index 19d1626..7cd510f 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -16,15 +16,27 @@
 
 package com.android.asllib.marshallable;
 
+import static org.junit.Assert.assertThrows;
+
 import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.ParserConfigurationException;
 
 @RunWith(JUnit4.class)
 public class SafetyLabelsTest {
+    private static final long DEFAULT_VERSION = 2L;
+
     private static final String SAFETY_LABELS_HR_PATH = "com/android/asllib/safetylabels/hr";
     private static final String SAFETY_LABELS_OD_PATH = "com/android/asllib/safetylabels/od";
 
@@ -52,29 +64,51 @@
         testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
     }
 
-    private void hrToOdExpectException(String fileName) {
-        TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
+    private void hrToOdExpectException(String fileName)
+            throws ParserConfigurationException, IOException, SAXException {
+        var safetyLabelsEle =
+                TestUtils.getElementFromResource(Paths.get(SAFETY_LABELS_HR_PATH, fileName));
+        assertThrows(
+                MalformedXmlException.class,
+                () ->
+                        new SafetyLabelsFactory()
+                                .createFromHrElement(safetyLabelsEle, DEFAULT_VERSION));
     }
 
-    private void odToHrExpectException(String fileName) {
-        TestUtils.odToHrExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_OD_PATH, fileName);
+    private void odToHrExpectException(String fileName)
+            throws ParserConfigurationException, IOException, SAXException {
+        var safetyLabelsEle =
+                TestUtils.getElementFromResource(Paths.get(SAFETY_LABELS_OD_PATH, fileName));
+        assertThrows(
+                MalformedXmlException.class,
+                () ->
+                        new SafetyLabelsFactory()
+                                .createFromOdElement(safetyLabelsEle, DEFAULT_VERSION));
     }
 
     private void testHrToOdSafetyLabels(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new SafetyLabelsFactory(),
-                SAFETY_LABELS_HR_PATH,
-                SAFETY_LABELS_OD_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        SafetyLabels safetyLabels =
+                new SafetyLabelsFactory()
+                        .createFromHrElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(SAFETY_LABELS_HR_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element appInfoEle = safetyLabels.toOdDomElement(doc);
+        doc.appendChild(appInfoEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(SAFETY_LABELS_OD_PATH, fileName));
     }
 
     private void testOdToHrSafetyLabels(String fileName) throws Exception {
-        TestUtils.testOdToHr(
-                TestUtils.document(),
-                new SafetyLabelsFactory(),
-                SAFETY_LABELS_OD_PATH,
-                SAFETY_LABELS_HR_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        SafetyLabels safetyLabels =
+                new SafetyLabelsFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(SAFETY_LABELS_OD_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element appInfoEle = safetyLabels.toHrDomElement(doc);
+        doc.appendChild(appInfoEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(SAFETY_LABELS_HR_PATH, fileName));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
index 87d3e44..9dcc652 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
@@ -16,15 +16,27 @@
 
 package com.android.asllib.marshallable;
 
+import static org.junit.Assert.assertThrows;
+
 import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.ParserConfigurationException;
 
 @RunWith(JUnit4.class)
 public class SystemAppSafetyLabelTest {
+    private static final long DEFAULT_VERSION = 2L;
+
     private static final String SYSTEM_APP_SAFETY_LABEL_HR_PATH =
             "com/android/asllib/systemappsafetylabel/hr";
     private static final String SYSTEM_APP_SAFETY_LABEL_OD_PATH =
@@ -57,31 +69,49 @@
         odToHrExpectException(MISSING_BOOL_FILE_NAME);
     }
 
-    private void hrToOdExpectException(String fileName) {
-        TestUtils.hrToOdExpectException(
-                new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName);
+    private void hrToOdExpectException(String fileName)
+            throws ParserConfigurationException, IOException, SAXException {
+        var ele =
+                TestUtils.getElementFromResource(
+                        Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName));
+        assertThrows(
+                MalformedXmlException.class,
+                () -> new SystemAppSafetyLabelFactory().createFromHrElement(ele, DEFAULT_VERSION));
     }
 
-    private void odToHrExpectException(String fileName) {
-        TestUtils.odToHrExpectException(
-                new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName);
+    private void odToHrExpectException(String fileName)
+            throws ParserConfigurationException, IOException, SAXException {
+        var ele =
+                TestUtils.getElementFromResource(
+                        Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName));
+        assertThrows(
+                MalformedXmlException.class,
+                () -> new SystemAppSafetyLabelFactory().createFromOdElement(ele, DEFAULT_VERSION));
     }
 
     private void testHrToOdSystemAppSafetyLabel(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new SystemAppSafetyLabelFactory(),
-                SYSTEM_APP_SAFETY_LABEL_HR_PATH,
-                SYSTEM_APP_SAFETY_LABEL_OD_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        SystemAppSafetyLabel systemAppSafetyLabel =
+                new SystemAppSafetyLabelFactory()
+                        .createFromHrElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element resultingEle = systemAppSafetyLabel.toOdDomElement(doc);
+        doc.appendChild(resultingEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName));
     }
 
     private void testOdToHrSystemAppSafetyLabel(String fileName) throws Exception {
-        TestUtils.testOdToHr(
-                TestUtils.document(),
-                new SystemAppSafetyLabelFactory(),
-                SYSTEM_APP_SAFETY_LABEL_OD_PATH,
-                SYSTEM_APP_SAFETY_LABEL_HR_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        SystemAppSafetyLabel systemAppSafetyLabel =
+                new SystemAppSafetyLabelFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element resultingEle = systemAppSafetyLabel.toHrDomElement(doc);
+        doc.appendChild(resultingEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
index 8a0b35e..6547fb9 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
@@ -22,9 +22,14 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+
+import java.nio.file.Paths;
 
 @RunWith(JUnit4.class)
 public class TransparencyInfoTest {
+    private static final long DEFAULT_VERSION = 2L;
+
     private static final String TRANSPARENCY_INFO_HR_PATH =
             "com/android/asllib/transparencyinfo/hr";
     private static final String TRANSPARENCY_INFO_OD_PATH =
@@ -45,20 +50,28 @@
     }
 
     private void testHrToOdTransparencyInfo(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new TransparencyInfoFactory(),
-                TRANSPARENCY_INFO_HR_PATH,
-                TRANSPARENCY_INFO_OD_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        TransparencyInfo transparencyInfo =
+                new TransparencyInfoFactory()
+                        .createFromHrElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(TRANSPARENCY_INFO_HR_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element resultingEle = transparencyInfo.toOdDomElement(doc);
+        doc.appendChild(resultingEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName));
     }
 
     private void testOdToHrTransparencyInfo(String fileName) throws Exception {
-        TestUtils.testOdToHr(
-                TestUtils.document(),
-                new TransparencyInfoFactory(),
-                TRANSPARENCY_INFO_OD_PATH,
-                TRANSPARENCY_INFO_HR_PATH,
-                fileName);
+        var doc = TestUtils.document();
+        TransparencyInfo transparencyInfo =
+                new TransparencyInfoFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName)),
+                                DEFAULT_VERSION);
+        Element resultingEle = transparencyInfo.toHrDomElement(doc);
+        doc.appendChild(resultingEle);
+        TestUtils.testFormatToFormat(doc, Paths.get(TRANSPARENCY_INFO_HR_PATH, fileName));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
index ea90993..f8ef40a 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -17,11 +17,8 @@
 package com.android.asllib.testutils;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
 
 import com.android.asllib.marshallable.AslMarshallable;
-import com.android.asllib.marshallable.AslMarshallableFactory;
-import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Document;
@@ -36,7 +33,6 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.List;
 import java.util.Optional;
 
@@ -60,7 +56,19 @@
     }
 
     /** Gets List of Element from a path to an existing Resource. */
-    public static List<Element> getElementsFromResource(Path filePath)
+    public static Element getElementFromResource(Path filePath)
+            throws ParserConfigurationException, IOException, SAXException {
+        String str = readStrFromResource(filePath);
+        InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        Document document = factory.newDocumentBuilder().parse(stream);
+        return document.getDocumentElement();
+    }
+
+    /** Gets List of Element from a path to an existing Resource. */
+    public static List<Element> getChildElementsFromResource(Path filePath)
             throws ParserConfigurationException, IOException, SAXException {
         String str = readStrFromResource(filePath);
         InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
@@ -69,16 +77,13 @@
         factory.setNamespaceAware(true);
         Document document = factory.newDocumentBuilder().parse(stream);
         Element root = document.getDocumentElement();
-        if (root.getTagName().equals(HOLDER_TAG_NAME)) {
-            String tagName =
-                    XmlUtils.asElementList(root.getChildNodes()).stream()
-                            .findFirst()
-                            .get()
-                            .getTagName();
-            return XmlUtils.getChildrenByTagName(root, tagName);
-        } else {
-            return List.of(root);
-        }
+
+        String tagName =
+                XmlUtils.asElementList(root.getChildNodes()).stream()
+                        .findFirst()
+                        .get()
+                        .getTagName();
+        return XmlUtils.getChildrenByTagName(root, tagName);
     }
 
     /** Reads a Document into a String. */
@@ -130,86 +135,13 @@
         return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
     }
 
-    /** Helper for testing human-readable to on-device conversion expecting exception */
-    public static <T extends AslMarshallable> void hrToOdExpectException(
-            AslMarshallableFactory<T> factory, String hrFolderPath, String fileName) {
-        assertThrows(
-                MalformedXmlException.class,
-                () -> {
-                    factory.createFromHrElements(
-                            TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
-                });
-    }
-
-    /** Helper for testing on-device to human-readable conversion expecting exception */
-    public static <T extends AslMarshallable> void odToHrExpectException(
-            AslMarshallableFactory<T> factory, String odFolderPath, String fileName) {
-        assertThrows(
-                MalformedXmlException.class,
-                () -> {
-                    factory.createFromOdElements(
-                            TestUtils.getElementsFromResource(Paths.get(odFolderPath, fileName)));
-                });
-    }
-
-    /** Helper for testing human-readable to on-device conversion */
-    public static <T extends AslMarshallable> void testHrToOd(
-            Document doc,
-            AslMarshallableFactory<T> factory,
-            String hrFolderPath,
-            String odFolderPath,
-            String fileName)
-            throws Exception {
-        testFormatToFormat(doc, factory, hrFolderPath, odFolderPath, fileName, true);
-    }
-
-    /** Helper for testing on-device to human-readable conversion */
-    public static <T extends AslMarshallable> void testOdToHr(
-            Document doc,
-            AslMarshallableFactory<T> factory,
-            String odFolderPath,
-            String hrFolderPath,
-            String fileName)
-            throws Exception {
-        testFormatToFormat(doc, factory, odFolderPath, hrFolderPath, fileName, false);
-    }
-
     /** Helper for testing format to format conversion */
-    private static <T extends AslMarshallable> void testFormatToFormat(
-            Document doc,
-            AslMarshallableFactory<T> factory,
-            String inFolderPath,
-            String outFolderPath,
-            String fileName,
-            boolean hrToOd)
-            throws Exception {
-        AslMarshallable marshallable =
-                hrToOd
-                        ? factory.createFromHrElements(
-                                TestUtils.getElementsFromResource(
-                                        Paths.get(inFolderPath, fileName)))
-                        : factory.createFromOdElements(
-                                TestUtils.getElementsFromResource(
-                                        Paths.get(inFolderPath, fileName)));
-
-        List<Element> elements =
-                hrToOd ? marshallable.toOdDomElements(doc) : marshallable.toHrDomElements(doc);
-        if (elements.isEmpty()) {
-            throw new IllegalStateException("elements was empty.");
-        } else if (elements.size() == 1) {
-            doc.appendChild(elements.get(0));
-        } else {
-            Element root = doc.createElement(TestUtils.HOLDER_TAG_NAME);
-            for (var child : elements) {
-                root.appendChild(child);
-            }
-            doc.appendChild(root);
-        }
+    public static <T extends AslMarshallable> void testFormatToFormat(
+            Document doc, Path expectedOutPath) throws Exception {
         String converted = TestUtils.getFormattedXml(TestUtils.docToStr(doc, true), true);
         System.out.println("Converted: " + converted);
         String expectedOutContents =
-                TestUtils.getFormattedXml(
-                        TestUtils.readStrFromResource(Paths.get(outFolderPath, fileName)), true);
+                TestUtils.getFormattedXml(TestUtils.readStrFromResource(expectedOutPath), true);
         System.out.println("Expected: " + expectedOutContents);
         assertEquals(expectedOutContents, converted);
     }
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
index ec0cd70..7478e39 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
@@ -1,3 +1,17 @@
 <app-metadata-bundles>
-
+    <system-app-safety-label declaration="true">
+    </system-app-safety-label>
+    <transparency-info>
+        <app-info
+            apsCompliant="false"
+            privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+            <first-party-endpoints>
+                <item>url1</item>
+            </first-party-endpoints>
+            <service-provider-endpoints>
+                <item>url55</item>
+                <item>url56</item>
+            </service-provider-endpoints>
+        </app-info>
+    </transparency-info>
 </app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
index 19bfd82..587c49a 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
@@ -1 +1,17 @@
-<app-metadata-bundles version="123456"></app-metadata-bundles>
\ No newline at end of file
+<app-metadata-bundles version="2">
+    <system-app-safety-label declaration="true">
+    </system-app-safety-label>
+    <transparency-info>
+        <app-info
+            apsCompliant="false"
+            privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+            <first-party-endpoints>
+                <item>url1</item>
+            </first-party-endpoints>
+            <service-provider-endpoints>
+                <item>url55</item>
+                <item>url56</item>
+            </service-provider-endpoints>
+        </app-info>
+    </transparency-info>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
index 03e71d2..9cfb8bc 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
@@ -1,4 +1,19 @@
-<app-metadata-bundles version="123456">
+<app-metadata-bundles version="2">
     <safety-labels>
     </safety-labels>
+    <system-app-safety-label declaration="true">
+    </system-app-safety-label>
+    <transparency-info>
+        <app-info
+            apsCompliant="false"
+            privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+            <first-party-endpoints>
+                <item>url1</item>
+            </first-party-endpoints>
+            <service-provider-endpoints>
+                <item>url55</item>
+                <item>url56</item>
+            </service-provider-endpoints>
+        </app-info>
+    </transparency-info>
 </app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
deleted file mode 100644
index afb0486..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<app-metadata-bundles version="123456">
-<system-app-safety-label declaration="true">
-</system-app-safety-label>
-</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
deleted file mode 100644
index a00ef65..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<app-metadata-bundles version="123456">
-    <transparency-info>
-        <app-info
-            apsCompliant="false"
-            privacyPolicy="www.example.com">
-            <first-party-endpoints>
-                <item>url1</item>
-            </first-party-endpoints>
-            <service-provider-endpoints>
-                <item>url55</item>
-                <item>url56</item>
-            </service-provider-endpoints>
-        </app-info>
-    </transparency-info>
-</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
index 1aa3aa9..9adfa98 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
@@ -1,2 +1,20 @@
 <bundle>
+    <pbundle_as_map name="system_app_safety_label">
+        <boolean name="declaration" value="true"/>
+    </pbundle_as_map>
+    <pbundle_as_map name="transparency_info">
+        <pbundle_as_map name="app_info">
+            <boolean name="aps_compliant" value="false"/>
+            <string name="privacy_policy" value="www.example.com"/>
+            <string-array name="first_party_endpoints" num="1">
+                <item value="url1"/>
+            </string-array>
+            <string-array name="service_provider_endpoints" num="2">
+                <item value="url55"/>
+                <item value="url56"/>
+            </string-array>
+            <string name="developer_id" value="dev1"/>
+            <string name="application_id" value="app1"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
 </bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
index 37bdfad..7a4c82a 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
@@ -1,3 +1,21 @@
 <bundle>
-    <long name="version" value="123456"/>
+    <long name="version" value="2"/>
+    <pbundle_as_map name="system_app_safety_label">
+        <boolean name="declaration" value="true"/>
+    </pbundle_as_map>
+    <pbundle_as_map name="transparency_info">
+        <pbundle_as_map name="app_info">
+            <string name="privacy_policy" value="www.example.com"/>
+            <string-array name="first_party_endpoints" num="1">
+                <item value="url1"/>
+            </string-array>
+            <string-array name="service_provider_endpoints" num="2">
+                <item value="url55"/>
+                <item value="url56"/>
+            </string-array>
+            <boolean name="aps_compliant" value="false"/>
+            <string name="developer_id" value="dev1"/>
+            <string name="application_id" value="app1"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
 </bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
index f00fb26..3a3e5d3 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
@@ -1,5 +1,22 @@
 <bundle>
-    <long name="version" value="123456"/>
-    <pbundle_as_map name="safety_labels">
+    <long name="version" value="2"/>
+    <pbundle_as_map name="safety_labels"/>
+    <pbundle_as_map name="system_app_safety_label">
+        <boolean name="declaration" value="true"/>
     </pbundle_as_map>
-</bundle>
+    <pbundle_as_map name="transparency_info">
+        <pbundle_as_map name="app_info">
+            <string name="privacy_policy" value="www.example.com"/>
+            <string-array name="first_party_endpoints" num="1">
+                <item value="url1"/>
+            </string-array>
+            <string-array name="service_provider_endpoints" num="2">
+                <item value="url55"/>
+                <item value="url56"/>
+            </string-array>
+            <boolean name="aps_compliant" value="false"/>
+            <string name="developer_id" value="dev1"/>
+            <string name="application_id" value="app1"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
deleted file mode 100644
index e8640c4..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<bundle>
-    <long name="version" value="123456"/>
-    <pbundle_as_map name="system_app_safety_label">
-        <boolean name="declaration" value="true"/>
-    </pbundle_as_map>
-</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
deleted file mode 100644
index d0c8668..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<bundle>
-    <long name="version" value="123456"/>
-    <pbundle_as_map name="transparency_info">
-        <pbundle_as_map name="app_info">
-            <boolean name="aps_compliant" value="false"/>
-            <string name="privacy_policy" value="www.example.com"/>
-            <string-array name="first_party_endpoints" num="1">
-                <item value="url1"/>
-            </string-array>
-            <string-array name="service_provider_endpoints" num="2">
-                <item value="url55"/>
-                <item value="url56"/>
-            </string-array>
-        </pbundle_as_map>
-    </pbundle_as_map>
-</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
index 0d15efc..306e015 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
@@ -1,6 +1,6 @@
 <app-info
     apsCompliant="false"
-    privacyPolicy="www.example.com">
+    privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
     <first-party-endpoints>
         <item>url1</item>
     </first-party-endpoints>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid-v1.xml
new file mode 100644
index 0000000..b026cf3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid-v1.xml
@@ -0,0 +1,24 @@
+<pbundle_as_map name="app_info">
+    <string name="title" value="beervision"/>
+    <string name="description" value="a beer app"/>
+    <boolean name="contains_ads" value="true"/>
+    <boolean name="obey_aps" value="false"/>
+    <boolean name="ads_fingerprinting" value="false"/>
+    <boolean name="security_fingerprinting" value="false"/>
+    <string name="privacy_policy" value="www.example.com"/>
+    <string-array name="security_endpoints" num="3">
+        <item value="url1"/>
+        <item value="url2"/>
+        <item value="url3"/>
+    </string-array>
+    <string-array name="first_party_endpoints" num="1">
+        <item value="url1"/>
+    </string-array>
+    <string-array name="service_provider_endpoints" num="2">
+        <item value="url55"/>
+        <item value="url56"/>
+    </string-array>
+    <string name="category" value="Food and drink"/>
+    <string name="email" value="max@maxloh.com"/>
+    <string name="website" value="www.example.com"/>
+</pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
index bce5179..7aae4a7 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
@@ -1,6 +1,5 @@
 
 <pbundle_as_map name="app_info">
-    <boolean name="aps_compliant" value="false"/>
     <string name="privacy_policy" value="www.example.com"/>
     <string-array name="first_party_endpoints" num="1">
         <item value="url1"/>
@@ -9,4 +8,7 @@
         <item value="url55"/>
         <item value="url56"/>
     </string-array>
+    <boolean name="aps_compliant" value="false"/>
+    <string name="developer_id" value="dev1"/>
+    <string name="application_id" value="app1"/>
 </pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml
new file mode 100644
index 0000000..810078e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml
@@ -0,0 +1,25 @@
+<pbundle_as_map name="app_info">
+    <string name="title" value="beervision"/>
+    <string name="description" value="a beer app"/>
+    <boolean name="contains_ads" value="true"/>
+    <boolean name="obey_aps" value="false"/>
+    <boolean name="ads_fingerprinting" value="false"/>
+    <boolean name="security_fingerprinting" value="false"/>
+    <string name="privacy_policy" value="www.example.com"/>
+    <string-array name="security_endpoints" num="3">
+        <item value="url1"/>
+        <item value="url2"/>
+        <item value="url3"/>
+    </string-array>
+    <string-array name="first_party_endpoints" num="1">
+        <item value="url1"/>
+    </string-array>
+    <string-array name="service_provider_endpoints" num="2">
+        <item value="url55"/>
+        <item value="url56"/>
+    </string-array>
+    <string name="category" value="Food and drink"/>
+    <string name="email" value="max@maxloh.com"/>
+    <string name="website" value="www.example.com"/>
+    <string name="unrecognized" value="www.example.com"/>
+</pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
index 2512ca4..2c5cf86 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
@@ -2,7 +2,7 @@
 <transparency-info>
     <app-info
         apsCompliant="false"
-        privacyPolicy="www.example.com">
+        privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
         <first-party-endpoints>
             <item>url1</item>
         </first-party-endpoints>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
index d16caae..29c88d2 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
@@ -1,5 +1,16 @@
 
 <transparency-info>
+    <app-info
+        apsCompliant="false"
+        privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+        <first-party-endpoints>
+            <item>url1</item>
+        </first-party-endpoints>
+        <service-provider-endpoints>
+            <item>url55</item>
+            <item>url56</item>
+        </service-provider-endpoints>
+    </app-info>
     <developer-info
         name="max"
         email="max@example.com"
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
index c7bdd97..c46cec1 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
@@ -1,7 +1,6 @@
 
 <pbundle_as_map name="transparency_info">
     <pbundle_as_map name="app_info">
-        <boolean name="aps_compliant" value="false"/>
         <string name="privacy_policy" value="www.example.com"/>
         <string-array name="first_party_endpoints" num="1">
             <item value="url1"/>
@@ -10,5 +9,8 @@
             <item value="url55"/>
             <item value="url56"/>
         </string-array>
+        <boolean name="aps_compliant" value="false"/>
+        <string name="developer_id" value="dev1"/>
+        <string name="application_id" value="app1"/>
     </pbundle_as_map>
 </pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
index d7a4e1a..b5e64b9 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
@@ -1,5 +1,18 @@
 
 <pbundle_as_map name="transparency_info">
+    <pbundle_as_map name="app_info">
+        <string name="privacy_policy" value="www.example.com"/>
+        <string-array name="first_party_endpoints" num="1">
+            <item value="url1"/>
+        </string-array>
+        <string-array name="service_provider_endpoints" num="2">
+            <item value="url55"/>
+            <item value="url56"/>
+        </string-array>
+        <boolean name="aps_compliant" value="false"/>
+        <string name="developer_id" value="dev1"/>
+        <string name="application_id" value="app1"/>
+    </pbundle_as_map>
     <pbundle_as_map name="developer_info">
         <string name="name" value="max"/>
         <string name="email" value="max@example.com"/>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general-v1/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general-v1/od.xml
new file mode 100644
index 0000000..e8b0c17
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general-v1/od.xml
@@ -0,0 +1,70 @@
+<bundle>
+    <long name="version" value="1"/>
+    <pbundle_as_map name="safety_labels">
+        <pbundle_as_map name="data_labels">
+            <pbundle_as_map name="data_shared">
+                <pbundle_as_map name="location">
+                    <pbundle_as_map name="approx_location">
+                        <int-array name="purposes" num="1">
+                            <item value="1"/>
+                        </int-array>
+                        <boolean name="is_sharing_optional" value="false"/>
+                        <boolean name="ephemeral" value="false"/>
+                    </pbundle_as_map>
+                    <pbundle_as_map name="precise_location">
+                        <int-array name="purposes" num="2">
+                            <item value="1"/>
+                            <item value="2"/>
+                        </int-array>
+                        <boolean name="is_sharing_optional" value="true"/>
+                        <boolean name="ephemeral" value="true"/>
+                    </pbundle_as_map>
+                </pbundle_as_map>
+            </pbundle_as_map>
+        </pbundle_as_map>
+        <pbundle_as_map name="security_labels">
+            <boolean name="is_data_deletable" value="true"/>
+            <boolean name="is_data_encrypted" value="false"/>
+        </pbundle_as_map>
+        <pbundle_as_map name="third_party_verification">
+            <string name="url" value="www.example.com"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
+    <pbundle_as_map name="system_app_safety_label">
+        <string name="url" value="www.example.com"/>
+    </pbundle_as_map>
+    <pbundle_as_map name="transparency_info">
+        <pbundle_as_map name="developer_info">
+            <string name="name" value="max"/>
+            <string name="email" value="max@example.com"/>
+            <string name="address" value="111 blah lane"/>
+            <string name="country_region" value="US"/>
+            <long name="relationship" value="5"/>
+            <string name="website" value="example.com"/>
+            <string name="app_developer_registry_id" value="registry_id"/>
+        </pbundle_as_map>
+        <pbundle_as_map name="app_info">
+            <string name="title" value="beervision"/>
+            <string name="description" value="a beer app"/>
+            <boolean name="contains_ads" value="true"/>
+            <boolean name="obey_aps" value="false"/>
+            <boolean name="ads_fingerprinting" value="false"/>
+            <boolean name="security_fingerprinting" value="false"/>
+            <string name="privacy_policy" value="www.example.com"/>
+            <string-array name="security_endpoints" num="3">
+                <item value="url1"/>
+                <item value="url2"/>
+                <item value="url3"/>
+            </string-array>
+            <string-array name="first_party_endpoints" num="1">
+                <item value="url1"/>
+            </string-array>
+            <string-array name="service_provider_endpoints" num="2">
+                <item value="url55"/>
+                <item value="url56"/>
+            </string-array>
+            <string name="category" value="Food and drink"/>
+            <string name="email" value="max@maxloh.com"/>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
index 5923079..f932982 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -1,20 +1,13 @@
-<app-metadata-bundles version="123">
+<app-metadata-bundles version="2">
     <safety-labels>
         <data-labels>
-            <data-shared
-                dataType="location_data_type_approx_location"
-                isSharingOptional="false"
-                purposes="app_functionality" />
-            <data-shared
-                dataType="location_data_type_precise_location"
-                isSharingOptional="true"
-                purposes="app_functionality|analytics" />
+            <data-shared dataType="location_data_type_approx_location" isSharingOptional="false" purposes="app_functionality"/>
+            <data-shared dataType="location_data_type_precise_location" isSharingOptional="true" purposes="app_functionality|analytics"/>
         </data-labels>
     </safety-labels>
-    <system-app-safety-label declaration="true">
-    </system-app-safety-label>
+    <system-app-safety-label declaration="true"/>
     <transparency-info>
-        <app-info apsCompliant="false" privacyPolicy="www.example.com">
+        <app-info applicationId="app1" apsCompliant="false" developerId="dev1" privacyPolicy="www.example.com">
             <first-party-endpoints>
                 <item>url1</item>
             </first-party-endpoints>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
index c24087e..c7def72 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -1,5 +1,5 @@
 <bundle>
-    <long name="version" value="123"/>
+    <long name="version" value="2"/>
     <pbundle_as_map name="safety_labels">
         <pbundle_as_map name="data_labels">
             <pbundle_as_map name="data_shared">
@@ -26,7 +26,6 @@
     </pbundle_as_map>
     <pbundle_as_map name="transparency_info">
         <pbundle_as_map name="app_info">
-            <boolean name="aps_compliant" value="false"/>
             <string name="privacy_policy" value="www.example.com"/>
             <string-array name="first_party_endpoints" num="1">
                 <item value="url1"/>
@@ -35,6 +34,9 @@
                 <item value="url55"/>
                 <item value="url56"/>
             </string-array>
+            <boolean name="aps_compliant" value="false"/>
+            <string name="developer_id" value="dev1"/>
+            <string name="application_id" value="app1"/>
         </pbundle_as_map>
     </pbundle_as_map>
 </bundle>
\ No newline at end of file