Merge "Switch all screenshot saving paths to ImageExporter" 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 1a930c5..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();
@@ -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/log/dagger/MediaLoadingLog.kt b/core/java/android/audio/policy/configuration/V7_0/package-info.java
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
copy to core/java/android/audio/policy/configuration/V7_0/package-info.java
index 05e1b2e..8f7425f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
+++ b/core/java/android/audio/policy/configuration/V7_0/package-info.java
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log.dagger
-
-import com.android.systemui.log.LogBuffer
-import javax.inject.Qualifier
-
-/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaLoadingLogger] */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class MediaLoadingLog
+/**
+ * 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/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/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/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 4706dfd..2c64b8e 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,19 +39,6 @@
     void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
 
     /**
-     * Registers remote animations per transition type for the organizer. It will override the
-     * animations if the transition only contains windows that belong to the organized
-     * TaskFragments in the given Task.
-     */
-    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
-        in RemoteAnimationDefinition definition);
-
-    /**
-     * Unregisters remote animations per transition type for the organizer.
-     */
-    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
-
-    /**
      * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
      * only occupies a portion of Task bounds.
      */
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 461eab6..d4c3fbe 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -32,7 +32,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 
 import com.android.window.flags.Flags;
@@ -205,34 +204,6 @@
     }
 
     /**
-     * Registers remote animations per transition type for the organizer. It will override the
-     * animations if the transition only contains windows that belong to the organized
-     * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
-     * @hide
-     */
-    @CallSuper
-    public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
-        try {
-            getController().registerRemoteAnimations(mInterface, definition);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Unregisters remote animations per transition type for the organizer.
-     * @hide
-     */
-    @CallSuper
-    public void unregisterRemoteAnimations() {
-        try {
-            getController().unregisterRemoteAnimations(mInterface);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Notifies the server that the organizer has finished handling the given transaction. The
      * server should apply the given {@link WindowContainerTransaction} for the necessary changes.
      *
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/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/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/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/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9ea2943..1eb95c1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -70,10 +70,6 @@
     @NonNull
     private final TaskFragmentCallback mCallback;
 
-    @VisibleForTesting
-    @Nullable
-    TaskFragmentAnimationController mAnimationController;
-
     /**
      * Callback that notifies the controller about changes to task fragments.
      */
@@ -91,25 +87,6 @@
         mCallback = callback;
     }
 
-    @Override
-    public void unregisterOrganizer() {
-        if (mAnimationController != null) {
-            mAnimationController.unregisterRemoteAnimations();
-            mAnimationController = null;
-        }
-        super.unregisterOrganizer();
-    }
-
-    /**
-     * Overrides the animation for transitions of embedded activities organized by this organizer.
-     */
-    void overrideSplitAnimation() {
-        if (mAnimationController == null) {
-            mAnimationController = new TaskFragmentAnimationController(this);
-        }
-        mAnimationController.registerRemoteAnimations();
-    }
-
     /**
      * Starts a new Activity and puts it into split with an existing Activity side-by-side.
      * @param launchingFragmentToken    token for the launching TaskFragment. If it exists, it will
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 7ddda1f..3261a37 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -114,7 +114,6 @@
 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
         ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
     static final String TAG = "SplitController";
-    static final boolean ENABLE_SHELL_TRANSITIONS = true;
 
     // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
     //  association. It's not set in WM Extensions nor Wm Jetpack library currently.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index eb1fc23..ea60b15 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -166,11 +166,6 @@
         mWindowLayoutComponent = windowLayoutComponent;
         mController = controller;
         registerOrganizer();
-        if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
-            // TODO(b/207070762): cleanup with legacy app transition
-            // Animation will be handled by WM Shell when Shell transition is enabled.
-            overrideSplitAnimation();
-        }
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
deleted file mode 100644
index 33220c4..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.graphics.Matrix.MTRANS_X;
-import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Choreographer;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import androidx.annotation.NonNull;
-
-/**
- * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
- *
- * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
- */
-class TaskFragmentAnimationAdapter {
-
-    /**
-     * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
-     */
-    private static final int LAYER_NO_OVERRIDE = -1;
-
-    @NonNull
-    final Animation mAnimation;
-    @NonNull
-    final RemoteAnimationTarget mTarget;
-    @NonNull
-    final SurfaceControl mLeash;
-    /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
-    @NonNull
-    private final Rect mWholeAnimationBounds = new Rect();
-    /**
-     * Area in absolute coordinate that should represent all the content to show for this window.
-     * This should be the end bounds for opening window, and start bounds for closing window in case
-     * the window is resizing during the open/close transition.
-     */
-    @NonNull
-    private final Rect mContentBounds = new Rect();
-    /** Offset relative to the window parent surface for {@link #mContentBounds}. */
-    @NonNull
-    private final Point mContentRelOffset = new Point();
-
-    @NonNull
-    final Transformation mTransformation = new Transformation();
-    @NonNull
-    final float[] mMatrix = new float[9];
-    @NonNull
-    final float[] mVecs = new float[4];
-    @NonNull
-    final Rect mRect = new Rect();
-    private boolean mIsFirstFrame = true;
-    private int mOverrideLayer = LAYER_NO_OVERRIDE;
-
-    TaskFragmentAnimationAdapter(@NonNull Animation animation,
-            @NonNull RemoteAnimationTarget target) {
-        this(animation, target, target.leash, target.screenSpaceBounds);
-    }
-
-    /**
-     * @param leash the surface to animate.
-     * @param wholeAnimationBounds  area in absolute coordinate that the animation surface shouldn't
-     *                              go beyond.
-     */
-    TaskFragmentAnimationAdapter(@NonNull Animation animation,
-            @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
-            @NonNull Rect wholeAnimationBounds) {
-        mAnimation = animation;
-        mTarget = target;
-        mLeash = leash;
-        mWholeAnimationBounds.set(wholeAnimationBounds);
-        if (target.mode == MODE_CLOSING) {
-            // When it is closing, we want to show the content at the start position in case the
-            // window is resizing as well. For example, when the activities is changing from split
-            // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
-            final Rect startBounds = target.startBounds;
-            final Rect endBounds = target.screenSpaceBounds;
-            mContentBounds.set(startBounds);
-            mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
-            mContentRelOffset.offset(
-                    startBounds.left - endBounds.left,
-                    startBounds.top - endBounds.top);
-        } else {
-            mContentBounds.set(target.screenSpaceBounds);
-            mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
-        }
-    }
-
-    /**
-     * Surface layer to be set at the first frame of the animation. We will not set the layer if it
-     * is set to {@link #LAYER_NO_OVERRIDE}.
-     */
-    final void overrideLayer(int layer) {
-        mOverrideLayer = layer;
-    }
-
-    /** Called on frame update. */
-    final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
-        if (mIsFirstFrame) {
-            t.show(mLeash);
-            if (mOverrideLayer != LAYER_NO_OVERRIDE) {
-                t.setLayer(mLeash, mOverrideLayer);
-            }
-            mIsFirstFrame = false;
-        }
-
-        // Extract the transformation to the current time.
-        mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
-                mTransformation);
-        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
-        onAnimationUpdateInner(t);
-    }
-
-    /** To be overridden by subclasses to adjust the animation surface change. */
-    void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
-        // Update the surface position and alpha.
-        mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
-        t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
-        t.setAlpha(mLeash, mTransformation.getAlpha());
-
-        // Get current surface bounds in absolute coordinate.
-        // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
-        final int positionX = Math.round(mMatrix[MTRANS_X]);
-        final int positionY = Math.round(mMatrix[MTRANS_Y]);
-        final Rect cropRect = new Rect(mContentBounds);
-        cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
-
-        // Store the current offset of the surface top left from (0,0) in absolute coordinate.
-        final int offsetX = cropRect.left;
-        final int offsetY = cropRect.top;
-
-        // Intersect to make sure the animation happens within the whole animation bounds.
-        if (!cropRect.intersect(mWholeAnimationBounds)) {
-            // Hide the surface when it is outside of the animation area.
-            t.setAlpha(mLeash, 0);
-        }
-
-        // cropRect is in absolute coordinate, so we need to translate it to surface top left.
-        cropRect.offset(-offsetX, -offsetY);
-        t.setCrop(mLeash, cropRect);
-    }
-
-    /** Called after animation finished. */
-    final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
-        onAnimationUpdate(t, mAnimation.getDuration());
-    }
-
-    final long getDurationHint() {
-        return mAnimation.computeDurationHint();
-    }
-
-    /**
-     * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
-     * size change.
-     */
-    static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
-
-        SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
-            // Start leash is the snapshot of the starting surface.
-            super(animation, target, target.startLeash, target.screenSpaceBounds);
-        }
-
-        @Override
-        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
-            // Snapshot should always be placed at the top left of the animation leash.
-            mTransformation.getMatrix().postTranslate(0, 0);
-            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
-            t.setAlpha(mLeash, mTransformation.getAlpha());
-        }
-    }
-
-    /**
-     * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
-     */
-    static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
-
-        BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
-            super(animation, target);
-        }
-
-        @Override
-        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
-            mTransformation.getMatrix().postTranslate(
-                    mTarget.localBounds.left, mTarget.localBounds.top);
-            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
-            t.setAlpha(mLeash, mTransformation.getAlpha());
-
-            // The following applies an inverse scale to the clip-rect so that it crops "after" the
-            // scale instead of before.
-            mVecs[1] = mVecs[2] = 0;
-            mVecs[0] = mVecs[3] = 1;
-            mTransformation.getMatrix().mapVectors(mVecs);
-            mVecs[0] = 1.f / mVecs[0];
-            mVecs[3] = 1.f / mVecs[3];
-            final Rect clipRect = mTransformation.getClipRect();
-            mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
-            mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
-            mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
-            mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
-            t.setWindowCrop(mLeash, mRect);
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
deleted file mode 100644
index d7eb9a0..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-
-import android.util.Log;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/** Controls the TaskFragment remote animations. */
-class TaskFragmentAnimationController {
-
-    private static final String TAG = "TaskFragAnimationCtrl";
-    static final boolean DEBUG = false;
-
-    private final TaskFragmentOrganizer mOrganizer;
-    private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
-    @VisibleForTesting
-    final RemoteAnimationDefinition mDefinition;
-    private boolean mIsRegistered;
-
-    TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
-        mOrganizer = organizer;
-        mDefinition = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter animationAdapter =
-                new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
-    }
-
-    void registerRemoteAnimations() {
-        if (DEBUG) {
-            Log.v(TAG, "registerRemoteAnimations");
-        }
-        if (mIsRegistered) {
-            return;
-        }
-        mOrganizer.registerRemoteAnimations(mDefinition);
-        mIsRegistered = true;
-    }
-
-    void unregisterRemoteAnimations() {
-        if (DEBUG) {
-            Log.v(TAG, "unregisterRemoteAnimations");
-        }
-        if (!mIsRegistered) {
-            return;
-        }
-        mOrganizer.unregisterRemoteAnimations();
-        mIsRegistered = false;
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
deleted file mode 100644
index d9b73a8..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_CHANGING;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
-
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.animation.Animation;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiFunction;
-
-/** To run the TaskFragment animations. */
-class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
-
-    private static final String TAG = "TaskFragAnimationRunner";
-    private final Handler mHandler;
-    private final TaskFragmentAnimationSpec mAnimationSpec;
-
-    TaskFragmentAnimationRunner() {
-        HandlerThread animationThread = new HandlerThread(
-                "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
-        animationThread.start();
-        mHandler = animationThread.getThreadHandler();
-        mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
-    }
-
-    @Nullable
-    private Animator mAnimator;
-
-    @Override
-    public void onAnimationStart(@WindowManager.TransitionOldType int transit,
-            @NonNull RemoteAnimationTarget[] apps,
-            @NonNull RemoteAnimationTarget[] wallpapers,
-            @NonNull RemoteAnimationTarget[] nonApps,
-            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
-        if (wallpapers.length != 0 || nonApps.length != 0) {
-            throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
-                    + "wallpaper or non-app windows.");
-        }
-        if (TaskFragmentAnimationController.DEBUG) {
-            Log.v(TAG, "onAnimationStart transit=" + transit);
-        }
-        mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
-    }
-
-    @Override
-    public void onAnimationCancelled() {
-        mHandler.post(this::cancelAnimation);
-    }
-
-    /** Creates and starts animation. */
-    private void startAnimation(@WindowManager.TransitionOldType int transit,
-            @NonNull RemoteAnimationTarget[] targets,
-            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
-        if (mAnimator != null) {
-            Log.w(TAG, "start new animation when the previous one is not finished yet.");
-            mAnimator.cancel();
-        }
-        mAnimator = createAnimator(transit, targets, finishedCallback);
-        mAnimator.start();
-    }
-
-    /** Cancels animation. */
-    private void cancelAnimation() {
-        if (mAnimator == null) {
-            return;
-        }
-        mAnimator.cancel();
-        mAnimator = null;
-    }
-
-    /** Creates the animator given the transition type and windows. */
-    @NonNull
-    private Animator createAnimator(@WindowManager.TransitionOldType int transit,
-            @NonNull RemoteAnimationTarget[] targets,
-            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
-        final List<TaskFragmentAnimationAdapter> adapters =
-                createAnimationAdapters(transit, targets);
-        long duration = 0;
-        for (TaskFragmentAnimationAdapter adapter : adapters) {
-            duration = Math.max(duration, adapter.getDurationHint());
-        }
-        final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
-        animator.setDuration(duration);
-        animator.addUpdateListener((anim) -> {
-            // Update all adapters in the same transaction.
-            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            for (TaskFragmentAnimationAdapter adapter : adapters) {
-                adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
-            }
-            t.apply();
-        });
-        animator.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {}
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                for (TaskFragmentAnimationAdapter adapter : adapters) {
-                    adapter.onAnimationEnd(t);
-                }
-                t.apply();
-
-                try {
-                    finishedCallback.onAnimationFinished();
-                } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
-                }
-                mAnimator = null;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {}
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {}
-        });
-        return animator;
-    }
-
-    /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
-    @NonNull
-    private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
-            @WindowManager.TransitionOldType int transit,
-            @NonNull RemoteAnimationTarget[] targets) {
-        switch (transit) {
-            case TRANSIT_OLD_ACTIVITY_OPEN:
-            case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
-                return createOpenAnimationAdapters(targets);
-            case TRANSIT_OLD_ACTIVITY_CLOSE:
-            case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
-                return createCloseAnimationAdapters(targets);
-            case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
-                return createChangeAnimationAdapters(targets);
-            default:
-                throw new IllegalArgumentException("Unhandled transit type=" + transit);
-        }
-    }
-
-    @NonNull
-    private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
-            @NonNull RemoteAnimationTarget[] targets) {
-        return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
-                mAnimationSpec::loadOpenAnimation);
-    }
-
-    @NonNull
-    private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
-            @NonNull RemoteAnimationTarget[] targets) {
-        return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
-                mAnimationSpec::loadCloseAnimation);
-    }
-
-    /**
-     * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
-     * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
-     */
-    @NonNull
-    private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
-            @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
-            @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
-        // We need to know if the target window is only a partial of the whole animation screen.
-        // If so, we will need to adjust it to make the whole animation screen looks like one.
-        final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
-        final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
-        final Rect openingWholeScreenBounds = new Rect();
-        final Rect closingWholeScreenBounds = new Rect();
-        for (RemoteAnimationTarget target : targets) {
-            if (target.mode != MODE_CLOSING) {
-                openingTargets.add(target);
-                openingWholeScreenBounds.union(target.screenSpaceBounds);
-            } else {
-                closingTargets.add(target);
-                closingWholeScreenBounds.union(target.screenSpaceBounds);
-                // Union the start bounds since this may be the ClosingChanging animation.
-                closingWholeScreenBounds.union(target.startBounds);
-            }
-        }
-
-        // For OPEN transition, open windows should be above close windows.
-        // For CLOSE transition, open windows should be below close windows.
-        int offsetLayer = TYPE_LAYER_OFFSET;
-        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
-        for (RemoteAnimationTarget target : openingTargets) {
-            final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
-                    animationProvider, openingWholeScreenBounds);
-            if (isOpening) {
-                adapter.overrideLayer(offsetLayer++);
-            }
-            adapters.add(adapter);
-        }
-        for (RemoteAnimationTarget target : closingTargets) {
-            final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
-                    animationProvider, closingWholeScreenBounds);
-            if (!isOpening) {
-                adapter.overrideLayer(offsetLayer++);
-            }
-            adapters.add(adapter);
-        }
-        return adapters;
-    }
-
-    @NonNull
-    private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
-            @NonNull RemoteAnimationTarget target,
-            @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
-            @NonNull Rect wholeAnimationBounds) {
-        final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
-        return new TaskFragmentAnimationAdapter(animation, target, target.leash,
-                wholeAnimationBounds);
-    }
-
-    @NonNull
-    private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
-            @NonNull RemoteAnimationTarget[] targets) {
-        if (shouldUseJumpCutForChangeAnimation(targets)) {
-            return new ArrayList<>();
-        }
-
-        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
-        for (RemoteAnimationTarget target : targets) {
-            if (target.mode == MODE_CHANGING) {
-                // This is the target with bounds change.
-                final Animation[] animations =
-                        mAnimationSpec.createChangeBoundsChangeAnimations(target);
-                // Adapter for the starting snapshot leash.
-                adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
-                        animations[0], target));
-                // Adapter for the ending bounds changed leash.
-                adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
-                        animations[1], target));
-                continue;
-            }
-
-            // These are the other targets that don't have bounds change in the same transition.
-            final Animation animation;
-            if (target.hasAnimatingParent) {
-                // No-op if it will be covered by the changing parent window.
-                animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
-            } else if (target.mode == MODE_CLOSING) {
-                animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
-            } else {
-                animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
-            }
-            adapters.add(new TaskFragmentAnimationAdapter(animation, target));
-        }
-        return adapters;
-    }
-
-    /**
-     * Whether we should use jump cut for the change transition.
-     * This normally happens when opening a new secondary with the existing primary using a
-     * different split layout. This can be complicated, like from horizontal to vertical split with
-     * new split pairs.
-     * Uses a jump cut animation to simplify.
-     */
-    private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
-        boolean hasOpeningWindow = false;
-        boolean hasClosingWindow = false;
-        for (RemoteAnimationTarget target : targets) {
-            if (target.hasAnimatingParent) {
-                continue;
-            }
-            hasOpeningWindow |= target.mode == MODE_OPENING;
-            hasClosingWindow |= target.mode == MODE_CLOSING;
-        }
-        return hasOpeningWindow && hasClosingWindow;
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
deleted file mode 100644
index 1f866c3..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.provider.Settings;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.R;
-import com.android.internal.policy.AttributeCache;
-import com.android.internal.policy.TransitionAnimation;
-
-/** Animation spec for TaskFragment transition. */
-// TODO(b/206557124): provide an easier way to customize animation
-class TaskFragmentAnimationSpec {
-
-    private static final String TAG = "TaskFragAnimationSpec";
-    private static final int CHANGE_ANIMATION_DURATION = 517;
-    private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
-    private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
-
-    private final Context mContext;
-    private final TransitionAnimation mTransitionAnimation;
-    private final Interpolator mFastOutExtraSlowInInterpolator;
-    private final LinearInterpolator mLinearInterpolator;
-    private float mTransitionAnimationScaleSetting;
-
-    TaskFragmentAnimationSpec(@NonNull Handler handler) {
-        mContext = ActivityThread.currentActivityThread().getApplication();
-        mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
-        // Initialize the AttributeCache for the TransitionAnimation.
-        AttributeCache.init(mContext);
-        mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.fast_out_extra_slow_in);
-        mLinearInterpolator = new LinearInterpolator();
-
-        // The transition animation should be adjusted based on the developer option.
-        final ContentResolver resolver = mContext.getContentResolver();
-        mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
-        resolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
-                new SettingsObserver(handler));
-    }
-
-    /** For target that doesn't need to be animated. */
-    @NonNull
-    static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
-        // Noop but just keep the target showing/hiding.
-        final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
-        return new AlphaAnimation(alpha, alpha);
-    }
-
-    /** Animation for target that is opening in a change transition. */
-    @NonNull
-    Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
-        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
-        final Rect bounds = target.screenSpaceBounds;
-        final int startLeft;
-        final int startTop;
-        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
-            // The window will be animated in from left or right depending on its position.
-            startTop = 0;
-            startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
-        } else {
-            // The window will be animated in from top or bottom depending on its position.
-            startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
-            startLeft = 0;
-        }
-
-        // The position should be 0-based as we will post translate in
-        // TaskFragmentAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
-        animation.setInterpolator(mFastOutExtraSlowInInterpolator);
-        animation.setDuration(CHANGE_ANIMATION_DURATION);
-        animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
-        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-        return animation;
-    }
-
-    /** Animation for target that is closing in a change transition. */
-    @NonNull
-    Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
-        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
-        // Use startBounds if the window is closing in case it may also resize.
-        final Rect bounds = target.startBounds;
-        final int endTop;
-        final int endLeft;
-        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
-            // The window will be animated out to left or right depending on its position.
-            endTop = 0;
-            endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
-        } else {
-            // The window will be animated out to top or bottom depending on its position.
-            endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
-            endLeft = 0;
-        }
-
-        // The position should be 0-based as we will post translate in
-        // TaskFragmentAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
-        animation.setInterpolator(mFastOutExtraSlowInInterpolator);
-        animation.setDuration(CHANGE_ANIMATION_DURATION);
-        animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
-        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-        return animation;
-    }
-
-    /**
-     * Animation for target that is changing (bounds change) in a change transition.
-     * @return the return array always has two elements. The first one is for the start leash, and
-     *         the second one is for the end leash.
-     */
-    @NonNull
-    Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
-        // Both start bounds and end bounds are in screen coordinates. We will post translate
-        // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
-        final Rect startBounds = target.startBounds;
-        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
-        final Rect endBounds = target.screenSpaceBounds;
-        float scaleX = ((float) startBounds.width()) / endBounds.width();
-        float scaleY = ((float) startBounds.height()) / endBounds.height();
-        // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
-        // be scaled up with its parent.
-        float startScaleX = 1.f / scaleX;
-        float startScaleY = 1.f / scaleY;
-
-        // The start leash will be fade out.
-        final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
-        final Animation startAlpha = new AlphaAnimation(1f, 0f);
-        startAlpha.setInterpolator(mLinearInterpolator);
-        startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
-        startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
-        startSet.addAnimation(startAlpha);
-        final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
-                startScaleY);
-        startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
-        startScale.setDuration(CHANGE_ANIMATION_DURATION);
-        startSet.addAnimation(startScale);
-        startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
-                endBounds.height());
-        startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-
-        // The end leash will be moved into the end position while scaling.
-        final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
-        endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
-        final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
-        endScale.setDuration(CHANGE_ANIMATION_DURATION);
-        endSet.addAnimation(endScale);
-        // The position should be 0-based as we will post translate in
-        // TaskFragmentAnimationAdapter#onAnimationUpdate
-        final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
-                startBounds.top - endBounds.top, 0);
-        endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
-        endSet.addAnimation(endTranslate);
-        // The end leash is resizing, we should update the window crop based on the clip rect.
-        final Rect startClip = new Rect(startBounds);
-        final Rect endClip = new Rect(endBounds);
-        startClip.offsetTo(0, 0);
-        endClip.offsetTo(0, 0);
-        final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
-        clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
-        endSet.addAnimation(clipAnim);
-        endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
-                parentBounds.height());
-        endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-
-        return new Animation[]{startSet, endSet};
-    }
-
-    @NonNull
-    Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
-            @NonNull Rect wholeAnimationBounds) {
-        final boolean isEnter = target.mode != MODE_CLOSING;
-        final Animation animation;
-        // Background color on TaskDisplayArea has already been set earlier in
-        // WindowContainer#getAnimationAdapter.
-        if (target.showBackdrop) {
-            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                    ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
-                    : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
-        } else {
-            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                    ? com.android.internal.R.anim.task_fragment_open_enter
-                    : com.android.internal.R.anim.task_fragment_open_exit);
-        }
-        // Use the whole animation bounds instead of the change bounds, so that when multiple change
-        // targets are opening at the same time, the animation applied to each will be the same.
-        // Otherwise, we may see gap between the activities that are launching together.
-        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
-                wholeAnimationBounds.width(), wholeAnimationBounds.height());
-        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-        return animation;
-    }
-
-    @NonNull
-    Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
-            @NonNull Rect wholeAnimationBounds) {
-        final boolean isEnter = target.mode != MODE_CLOSING;
-        final Animation animation;
-        if (target.showBackdrop) {
-            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                    ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
-                    : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
-        } else {
-            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                    ? com.android.internal.R.anim.task_fragment_close_enter
-                    : com.android.internal.R.anim.task_fragment_close_exit);
-        }
-        // Use the whole animation bounds instead of the change bounds, so that when multiple change
-        // targets are closing at the same time, the animation applied to each will be the same.
-        // Otherwise, we may see gap between the activities that are finishing together.
-        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
-                wholeAnimationBounds.width(), wholeAnimationBounds.height());
-        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-        return animation;
-    }
-
-    private float getTransitionAnimationScaleSetting() {
-        return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
-                Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
-                                R.dimen.config_appTransitionAnimationDurationScaleDefault)));
-    }
-
-    private class SettingsObserver extends ContentObserver {
-        SettingsObserver(@NonNull Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 7b473b0..ad41b18 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -23,8 +23,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -86,24 +84,6 @@
     }
 
     @Test
-    public void testUnregisterOrganizer() {
-        mOrganizer.overrideSplitAnimation();
-        mOrganizer.unregisterOrganizer();
-
-        verify(mOrganizer).unregisterRemoteAnimations();
-    }
-
-    @Test
-    public void testOverrideSplitAnimation() {
-        assertNull(mOrganizer.mAnimationController);
-
-        mOrganizer.overrideSplitAnimation();
-
-        assertNotNull(mOrganizer.mAnimationController);
-        verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
-    }
-
-    @Test
     public void testExpandTaskFragment() {
         final TaskContainer taskContainer = createTestTaskContainer();
         doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
deleted file mode 100644
index a1e9f08..0000000
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.mockito.Mockito.never;
-
-import android.platform.test.annotations.Presubmit;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Test class for {@link TaskFragmentAnimationController}.
- *
- * Build/Install/Run:
- *  atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaskFragmentAnimationControllerTest {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    @Mock
-    private TaskFragmentOrganizer mOrganizer;
-    private TaskFragmentAnimationController mAnimationController;
-
-    @Before
-    public void setup() {
-        mAnimationController = new TaskFragmentAnimationController(mOrganizer);
-    }
-
-    @Test
-    public void testRegisterRemoteAnimations() {
-        mAnimationController.registerRemoteAnimations();
-
-        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
-
-        mAnimationController.registerRemoteAnimations();
-
-        // No extra call if it has been registered.
-        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
-    }
-
-    @Test
-    public void testUnregisterRemoteAnimations() {
-        mAnimationController.unregisterRemoteAnimations();
-
-        // No call if it is not registered.
-        verify(mOrganizer, never()).unregisterRemoteAnimations();
-
-        mAnimationController.registerRemoteAnimations();
-        mAnimationController.unregisterRemoteAnimations();
-
-        verify(mOrganizer).unregisterRemoteAnimations();
-
-        mAnimationController.unregisterRemoteAnimations();
-
-        // No extra call if it has been unregistered.
-        verify(mOrganizer).unregisterRemoteAnimations();
-    }
-}
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/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/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 e86f6a1..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
@@ -107,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;
@@ -160,7 +160,7 @@
 
     WindowDecoration(
             Context context,
-            DisplayController displayController,
+            @NonNull DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
             RunningTaskInfo taskInfo,
             @NonNull SurfaceControl taskSurface,
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/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/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/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/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/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/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 61dc17d..28e88d2 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1153,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/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/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/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/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index 0551bfb..067b00c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -33,13 +33,14 @@
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
 import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.mockMediaLogger
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
 import com.android.systemui.statusbar.notificationLockscreenUserManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
@@ -48,12 +49,16 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
 import org.mockito.Mockito
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MediaCarouselViewModelTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos()
+    private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
     private val testScope = kosmos.testScope
 
     private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
@@ -166,6 +171,64 @@
             assertThat(mediaControl.isMediaFromRec).isTrue()
         }
 
+    @Test
+    fun addMediaControlThenRemove_mediaEventsAreLogged() =
+        testScope.runTest {
+            val sortedMedia by collectLastValue(underTest.mediaItems)
+            val instanceId = InstanceId.fakeInstanceId(123)
+
+            loadMediaControl(KEY, instanceId)
+
+            val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
+            assertThat(mediaControl.instanceId).isEqualTo(instanceId)
+
+            // when media control is added to carousel
+            mediaControl.onAdded(mediaControl)
+
+            verify(kosmos.mediaLogger).logMediaCardAdded(eq(instanceId))
+
+            reset(kosmos.mediaLogger)
+
+            // when media control is updated.
+            mediaControl.onUpdated(mediaControl)
+
+            verify(kosmos.mediaLogger, never()).logMediaCardAdded(eq(instanceId))
+
+            mediaDataFilter.onMediaDataRemoved(KEY, true)
+            assertThat(sortedMedia).isEmpty()
+
+            // when media control is removed from carousel
+            mediaControl.onRemoved(true)
+
+            verify(kosmos.mediaLogger).logMediaCardRemoved(eq(instanceId))
+        }
+
+    @Test
+    fun addMediaRecommendationThenRemove_mediaEventsAreLogged() =
+        testScope.runTest {
+            val sortedMedia by collectLastValue(underTest.mediaItems)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+
+            loadMediaRecommendations()
+
+            val mediaRecommendations =
+                sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
+            assertThat(mediaRecommendations.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
+
+            // when media recommendation is added to carousel
+            mediaRecommendations.onAdded(mediaRecommendations)
+
+            verify(kosmos.mediaLogger).logMediaRecommendationCardAdded(eq(KEY_MEDIA_SMARTSPACE))
+
+            mediaDataFilter.onSmartspaceMediaDataRemoved(KEY, true)
+            assertThat(sortedMedia).isEmpty()
+
+            // when media recommendation is removed from carousel
+            mediaRecommendations.onRemoved(true)
+
+            verify(kosmos.mediaLogger).logMediaRecommendationCardRemoved(eq(KEY_MEDIA_SMARTSPACE))
+        }
+
     private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) {
         whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
         whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
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/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/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/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 c1e99db..a6dd14a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3597,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/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/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/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/log/dagger/MediaLoadingLog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
index 05e1b2e..51631b1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
@@ -14,13 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log.dagger
+package com.android.systemui.keyboard.shortcut.qualifiers
 
-import com.android.systemui.log.LogBuffer
 import javax.inject.Qualifier
 
-/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaLoadingLogger] */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class MediaLoadingLog
+@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/log/dagger/MediaLoadingLog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
index 05e1b2e..7fc0103 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log.dagger
+package com.android.systemui.keyboard.shortcut.ui.model
 
-import com.android.systemui.log.LogBuffer
-import javax.inject.Qualifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
 
-/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaLoadingLogger] */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class MediaLoadingLog
+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/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/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 52b0b87..d1c9b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -369,9 +369,9 @@
      */
     @Provides
     @SysUISingleton
-    @MediaLoadingLog
-    public static LogBuffer providesMediaLoadingLogBuffer(LogBufferFactory factory) {
-        return factory.create("MediaLoadingLog", 20);
+    @MediaLog
+    public static LogBuffer providesMediaLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaLog", 20);
     }
 
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLog.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/MediaLog.kt
index 05e1b2e..d1dc6f3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLoadingLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaLog.kt
@@ -19,8 +19,5 @@
 import com.android.systemui.log.LogBuffer
 import javax.inject.Qualifier
 
-/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaLoadingLogger] */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class MediaLoadingLog
+/** A [LogBuffer] for [com.android.systemui.media.controls.shared.MediaLogger] */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class MediaLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index 803e7ef..68f1af3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.data.repository.MediaFilterRepository
+import com.android.systemui.media.controls.shared.MediaLogger
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
@@ -70,7 +71,7 @@
     private val logger: MediaUiEventLogger,
     private val mediaFlags: MediaFlags,
     private val mediaFilterRepository: MediaFilterRepository,
-    private val mediaLoadingLogger: MediaLoadingLogger,
+    private val mediaLogger: MediaLogger,
 ) : MediaDataManager.Listener {
     /** Non-UI listeners to media changes. */
     private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf()
@@ -118,7 +119,7 @@
 
         val isUpdate = mediaFilterRepository.addSelectedUserMediaEntry(data)
 
-        mediaLoadingLogger.logMediaLoaded(data.instanceId, data.active, "loading media")
+        mediaLogger.logMediaLoaded(data.instanceId, data.active, "loading media")
         mediaFilterRepository.addMediaDataLoadingState(
             MediaDataLoadingModel.Loaded(data.instanceId),
             isUpdate
@@ -189,7 +190,7 @@
                         isSsReactivated = true
                     )
                 )
-                mediaLoadingLogger.logMediaLoaded(
+                mediaLogger.logMediaLoaded(
                     mediaData.instanceId,
                     mediaData.active,
                     "reactivating media instead of smartspace"
@@ -226,7 +227,7 @@
         mediaFilterRepository.setRecommendationsLoadingState(
             SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
         )
-        mediaLoadingLogger.logRecommendationLoaded(key, data.isActive, "loading recommendations")
+        mediaLogger.logRecommendationLoaded(key, data.isActive, "loading recommendations")
         listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
@@ -237,7 +238,7 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Removed(instanceId)
                 )
-                mediaLoadingLogger.logMediaRemoved(instanceId, "removing media card")
+                mediaLogger.logMediaRemoved(instanceId, "removing media card")
                 // Only notify listeners if something actually changed
                 listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
             }
@@ -253,11 +254,7 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Loaded(lastActiveId, immediately)
                 )
-                mediaLoadingLogger.logMediaLoaded(
-                    lastActiveId,
-                    it.active,
-                    "expiring reactivated id"
-                )
+                mediaLogger.logMediaLoaded(lastActiveId, it.active, "expiring reactivated id")
                 listeners.forEach { listener ->
                     getKey(lastActiveId)?.let { lastActiveKey ->
                         listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately)
@@ -278,11 +275,7 @@
         mediaFilterRepository.setRecommendationsLoadingState(
             SmartspaceMediaLoadingModel.Removed(key, immediately)
         )
-        mediaLoadingLogger.logRecommendationRemoved(
-            key,
-            immediately,
-            "removing recommendations card"
-        )
+        mediaLogger.logRecommendationRemoved(key, immediately, "removing recommendations card")
         listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
     }
 
@@ -296,10 +289,7 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Removed(data.instanceId)
                 )
-                mediaLoadingLogger.logMediaRemoved(
-                    data.instanceId,
-                    "Removing $key after profile change"
-                )
+                mediaLogger.logMediaRemoved(data.instanceId, "Removing $key after profile change")
                 listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
             }
         }
@@ -316,7 +306,7 @@
             mediaFilterRepository.addMediaDataLoadingState(
                 MediaDataLoadingModel.Removed(instanceId)
             )
-            mediaLoadingLogger.logMediaRemoved(instanceId, "Removing media after user change")
+            mediaLogger.logMediaRemoved(instanceId, "Removing media after user change")
             getKey(instanceId)?.let {
                 listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
             }
@@ -329,7 +319,7 @@
                     MediaDataLoadingModel.Loaded(data.instanceId),
                     isUpdate
                 )
-                mediaLoadingLogger.logMediaLoaded(
+                mediaLogger.logMediaLoaded(
                     data.instanceId,
                     data.active,
                     "Re-adding $key after user change"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaLoadingLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaLoadingLogger.kt
rename to packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index c6cfd65..2b710b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaLoadingLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.domain.pipeline
+package com.android.systemui.media.controls.shared
 
 import com.android.internal.logging.InstanceId
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.MediaLoadingLog
+import com.android.systemui.log.dagger.MediaLog
 import javax.inject.Inject
 
 /** A buffered log for media loading events. */
 @SysUISingleton
-class MediaLoadingLogger @Inject constructor(@MediaLoadingLog private val buffer: LogBuffer) {
+class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
 
     fun logMediaLoaded(instanceId: InstanceId, active: Boolean, reason: String) {
         buffer.log(
@@ -78,7 +78,43 @@
         )
     }
 
+    fun logMediaCardAdded(instanceId: InstanceId) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = instanceId.toString() },
+            { "adding media card $str1 to carousel" }
+        )
+    }
+
+    fun logMediaCardRemoved(instanceId: InstanceId) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = instanceId.toString() },
+            { "removing media card $str1 from carousel" }
+        )
+    }
+
+    fun logMediaRecommendationCardAdded(key: String) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = key },
+            { "adding recommendation card $str1 to carousel" }
+        )
+    }
+
+    fun logMediaRecommendationCardRemoved(key: String) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = key },
+            { "removing recommendation card $str1 from carousel" }
+        )
+    }
+
     companion object {
-        private const val TAG = "MediaLoadingLog"
+        private const val TAG = "MediaLog"
     }
 }
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/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index c453a21..e7f7171 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.media.controls.domain.pipeline.interactor.factory.MediaControlInteractorFactory
+import com.android.systemui.media.controls.shared.MediaLogger
 import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -52,6 +53,7 @@
     private val recommendationsViewModel: MediaRecommendationsViewModel,
     private val logger: MediaUiEventLogger,
     private val mediaFlags: MediaFlags,
+    private val mediaLogger: MediaLogger,
 ) {
 
     val mediaItems: StateFlow<List<MediaCommonViewModel>> =
@@ -131,10 +133,14 @@
                     instanceId = instanceId,
                     immediatelyUpdateUi = commonModel.mediaLoadedModel.immediatelyUpdateUi,
                     controlViewModel = createMediaControlViewModel(instanceId),
-                    onAdded = { onMediaControlAddedOrUpdated(it, commonModel) },
+                    onAdded = {
+                        mediaLogger.logMediaCardAdded(instanceId)
+                        onMediaControlAddedOrUpdated(it, commonModel)
+                    },
                     onRemoved = {
                         interactor.removeMediaControl(instanceId, delay = 0L)
                         mediaControlByInstanceId.remove(instanceId)
+                        mediaLogger.logMediaCardRemoved(instanceId)
                     },
                     onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) },
                     isMediaFromRec = commonModel.isMediaFromRec,
@@ -168,6 +174,9 @@
                             mediaFlags.isPersistentSsCardEnabled(),
                     recsViewModel = recommendationsViewModel,
                     onAdded = { commonViewModel ->
+                        mediaLogger.logMediaRecommendationCardAdded(
+                            commonModel.recsLoadingModel.key
+                        )
                         onMediaRecommendationAddedOrUpdated(
                             commonViewModel as MediaCommonViewModel.MediaRecommendations
                         )
@@ -215,6 +224,7 @@
         commonModel: MediaCommonModel.MediaRecommendations,
         immediatelyRemove: Boolean
     ) {
+        mediaLogger.logMediaRecommendationCardRemoved(commonModel.recsLoadingModel.key)
         if (immediatelyRemove || isReorderingAllowed()) {
             interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
             mediaRecs = null
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/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/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 2f87b01..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
@@ -465,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/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/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/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/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/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/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/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/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/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/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/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/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 850e2e0..3705909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.data.repository.MediaFilterRepository
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.mockMediaLogger
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
 import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.shared.model.MediaData
@@ -114,7 +115,7 @@
             mediaSmartspaceLogger = mockMediaSmartspaceLogger
             mediaFilterRepository
         }
-    private val mediaLoadingLogger = kosmos.mockMediaLoadingLogger
+    private val mediaLogger = kosmos.mockMediaLogger
 
     @Before
     fun setup() {
@@ -133,7 +134,7 @@
                 logger,
                 mediaFlags,
                 repository,
-                mediaLoadingLogger,
+                mediaLogger,
             )
         mediaDataFilter.mediaDataProcessor = mediaDataProcessor
         mediaDataFilter.addListener(listener)
@@ -194,7 +195,7 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataMain.instanceId), eq(dataMain.active), anyString())
             assertThat(currentMedia).containsExactly(mediaCommonModel)
         }
@@ -210,7 +211,7 @@
 
             verify(listener, never())
                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
-            verify(mediaLoadingLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
             assertThat(currentMedia).doesNotContain(mediaCommonModel)
         }
 
@@ -224,14 +225,14 @@
             // GIVEN a media was removed for main user
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
 
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataMain.instanceId), eq(dataMain.active), anyString())
             assertThat(currentMedia).containsExactly(mediaCommonModel)
 
             mediaDataFilter.onMediaDataRemoved(KEY, false)
 
             verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
-            verify(mediaLoadingLogger).logMediaRemoved(eq(dataMain.instanceId), anyString())
+            verify(mediaLogger).logMediaRemoved(eq(dataMain.instanceId), anyString())
             assertThat(currentMedia).doesNotContain(mediaCommonModel)
         }
 
@@ -245,8 +246,7 @@
             mediaDataFilter.onMediaDataRemoved(KEY, false)
 
             verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
-            verify(mediaLoadingLogger, never())
-                .logMediaRemoved(eq(dataGuest.instanceId), anyString())
+            verify(mediaLogger, never()).logMediaRemoved(eq(dataGuest.instanceId), anyString())
             assertThat(currentMedia).isEmpty()
         }
 
@@ -259,7 +259,7 @@
             // GIVEN that we have a media loaded for main user
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
 
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataMain.instanceId), eq(dataMain.active), anyString())
             assertThat(currentMedia).containsExactly(MediaCommonModel.MediaControl(mediaLoaded))
 
@@ -268,7 +268,7 @@
 
             // THEN we should remove the main user's media
             verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
-            verify(mediaLoadingLogger).logMediaRemoved(eq(dataMain.instanceId), anyString())
+            verify(mediaLogger).logMediaRemoved(eq(dataMain.instanceId), anyString())
             assertThat(currentMedia).isEmpty()
         }
 
@@ -289,10 +289,10 @@
             // THEN we should add back the guest user media
             verify(listener)
                 .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataGuest.instanceId), eq(dataGuest.active), anyString())
 
-            reset(mediaLoadingLogger)
+            reset(mediaLogger)
 
             // but not the main user's
             verify(listener, never())
@@ -304,7 +304,7 @@
                     anyInt(),
                     anyBoolean()
                 )
-            verify(mediaLoadingLogger, never())
+            verify(mediaLogger, never())
                 .logMediaLoaded(eq(dataMain.instanceId), anyBoolean(), anyString())
             assertThat(currentMedia)
                 .containsExactly(MediaCommonModel.MediaControl(guestLoadedStatesModel))
@@ -327,7 +327,7 @@
             val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
             // THEN we should remove the private profile media
             verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
-            verify(mediaLoadingLogger).logMediaRemoved(eq(dataGuest.instanceId), anyString())
+            verify(mediaLogger).logMediaRemoved(eq(dataGuest.instanceId), anyString())
             assertThat(currentMedia)
                 .containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel))
         }
@@ -591,8 +591,7 @@
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -622,9 +621,8 @@
             verify(listener, never())
                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
-            verify(mediaLoadingLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
-            verify(mediaLoadingLogger, never())
-                .logRecommendationLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logRecommendationLoaded(any(), anyBoolean(), anyString())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -662,8 +660,7 @@
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -698,8 +695,7 @@
                 .isFalse()
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
-            verify(mediaLoadingLogger, never())
-                .logRecommendationLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logRecommendationLoaded(any(), anyBoolean(), anyString())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -727,10 +723,10 @@
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
 
-            reset(mediaLoadingLogger)
+            reset(mediaLogger)
 
             // AND we get a smartspace signal
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -749,10 +745,9 @@
             verify(listener, never())
                 .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
-            verify(mediaLoadingLogger, never())
+            verify(mediaLogger, never())
                 .logMediaLoaded(eq(dataCurrent.instanceId), anyBoolean(), anyString())
-            verify(mediaLoadingLogger, never())
-                .logRecommendationLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logRecommendationLoaded(any(), anyBoolean(), anyString())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -775,7 +770,7 @@
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
 
             // AND we get a smartspace signal
@@ -810,7 +805,7 @@
                     eq(100),
                     eq(true)
                 )
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(
                     eq(dataCurrentAndActive.instanceId),
                     eq(dataCurrentAndActive.active),
@@ -818,8 +813,7 @@
                 )
             // Smartspace update shouldn't be propagated for the empty rec list.
             verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
-            verify(mediaLoadingLogger, never())
-                .logRecommendationLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logRecommendationLoaded(any(), anyBoolean(), anyString())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
         }
@@ -846,7 +840,7 @@
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
 
             // AND we get a smartspace signal
@@ -865,7 +859,7 @@
                     eq(100),
                     eq(true)
                 )
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(
                     eq(dataCurrentAndActive.instanceId),
                     eq(dataCurrentAndActive.active),
@@ -890,8 +884,7 @@
             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
             verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
         }
@@ -908,8 +901,7 @@
             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
             verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
-            verify(mediaLoadingLogger)
-                .logRecommendationRemoved(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationRemoved(eq(SMARTSPACE_KEY), eq(true), anyString())
             assertThat(currentMedia).isEmpty()
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -941,7 +933,7 @@
             assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
 
             runCurrent()
@@ -958,7 +950,7 @@
                     eq(100),
                     eq(true)
                 )
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(
                     eq(dataCurrentAndActive.instanceId),
                     eq(dataCurrentAndActive.active),
@@ -968,8 +960,7 @@
             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
             verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
-            verify(mediaLoadingLogger)
-                .logRecommendationRemoved(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationRemoved(eq(SMARTSPACE_KEY), eq(true), anyString())
             assertThat(currentMedia).containsExactly(controlCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -1000,8 +991,7 @@
 
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(false), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(false), anyString())
             assertThat(currentMedia).containsExactly(recsCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -1042,11 +1032,11 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
             assertThat(currentMedia).containsExactly(controlCommonModel)
 
-            reset(mediaLoadingLogger)
+            reset(mediaLogger)
 
             // And an inactive recommendation is loaded
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -1054,11 +1044,10 @@
             // Smartspace is loaded but the media stays inactive
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(false), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(false), anyString())
             verify(listener, never())
                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
-            verify(mediaLoadingLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
+            verify(mediaLogger, never()).logMediaLoaded(any(), anyBoolean(), anyString())
             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -1127,7 +1116,7 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
             assertThat(currentMedia).containsExactly(controlCommonModel)
 
@@ -1156,7 +1145,7 @@
                     eq(100),
                     eq(true)
                 )
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(
                     eq(dataCurrentAndActive.instanceId),
                     eq(dataCurrentAndActive.active),
@@ -1174,8 +1163,7 @@
             // And update the smartspace data state, but not prioritized
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
         }
 
     @Test
@@ -1199,11 +1187,11 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            verify(mediaLoadingLogger)
+            verify(mediaLogger)
                 .logMediaLoaded(eq(dataCurrent.instanceId), eq(dataCurrent.active), anyString())
             assertThat(currentMedia).containsExactly(controlCommonModel)
 
-            reset(mediaLoadingLogger)
+            reset(mediaLogger)
 
             // AND we get a smartspace signal with extra to not trigger resume
             val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
@@ -1213,13 +1201,12 @@
             // THEN listeners are not updated to show media
             verify(listener, never())
                 .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
-            verify(mediaLoadingLogger, never())
+            verify(mediaLogger, never())
                 .logMediaLoaded(eq(dataCurrent.instanceId), anyBoolean(), anyString())
             // But the smartspace update is still propagated
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            verify(mediaLoadingLogger)
-                .logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
+            verify(mediaLogger).logRecommendationLoaded(eq(SMARTSPACE_KEY), eq(true), anyString())
             assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
         }
 
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/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/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index fe29140..18a62cf 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
@@ -306,14 +291,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
@@ -330,14 +309,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
@@ -359,13 +332,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/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 0a06cc7..90f94c8 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
@@ -244,14 +217,8 @@
             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/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 3028d00..29fd792 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
@@ -193,14 +178,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
@@ -216,13 +195,7 @@
             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/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/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/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/media/controls/domain/pipeline/MediaDataFilterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
index b8b0060..1473184 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.mediaLogger
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.media.controls.util.mediaUiEventLogger
 import com.android.systemui.settings.userTracker
@@ -45,6 +46,6 @@
             logger = mediaUiEventLogger,
             mediaFlags = mediaFlags,
             mediaFilterRepository = mediaFilterRepository,
-            mediaLoadingLogger = mediaLoadingLogger,
+            mediaLogger = mediaLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaLoadingLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/shared/MediaLoggerKosmos.kt
similarity index 73%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaLoadingLoggerKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/shared/MediaLoggerKosmos.kt
index 96886f7..55c419e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaLoadingLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/shared/MediaLoggerKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.controls.domain.pipeline
+package com.android.systemui.media.controls.shared
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.log.logcatLogBuffer
 import org.mockito.Mockito.mock
 
-val Kosmos.mediaLoadingLogger by
-    Kosmos.Fixture { MediaLoadingLogger(logcatLogBuffer("MediaLoadingLogBuffer")) }
-val Kosmos.mockMediaLoadingLogger by Kosmos.Fixture { mock(MediaLoadingLogger::class.java) }
+var Kosmos.mediaLogger by Kosmos.Fixture { MediaLogger(logcatLogBuffer("MediaLogBuffer")) }
+val Kosmos.mockMediaLogger by Kosmos.Fixture { mock(MediaLogger::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
index 069995a..054ac2e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.media.controls.domain.pipeline.interactor.factory.mediaControlInteractorFactory
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.mediaLogger
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.media.controls.util.mediaUiEventLogger
 import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
@@ -40,5 +41,6 @@
             recommendationsViewModel = mediaRecommendationsViewModel,
             logger = mediaUiEventLogger,
             mediaFlags = mediaFlags,
+            mediaLogger = mediaLogger,
         )
     }
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/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/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..2c233f8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1901,6 +1901,7 @@
                 Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
                 return;
             }
+            UserManager.invalidateQuietModeEnabledCache();
             profile.flags ^= UserInfo.FLAG_QUIET_MODE;
             profileUserData = getUserDataLU(profile.id);
         }
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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5e644d3..834c17e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -232,6 +232,7 @@
 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;
@@ -9283,18 +9284,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) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 44b414f..78636a7 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -98,7 +98,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -290,12 +289,10 @@
                 getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
         final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
 
-        // Check if there is any override
-        if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
-            // Unfreeze the windows that were previously frozen for TaskFragment animation.
-            unfreezeEmbeddedChangingWindows();
-            overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
-        }
+        // No AE remote animation with Shell transition.
+        // Unfreeze the windows that were previously frozen for TaskFragment animation.
+        unfreezeEmbeddedChangingWindows();
+        overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
 
         final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
                 || containsVoiceInteraction(mDisplayContent.mOpeningApps);
@@ -726,64 +723,6 @@
     }
 
     /**
-     * Overrides the pending transition with the remote animation defined by the
-     * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
-     * {@link TaskFragment} that are organized by the same organizer.
-     *
-     * @return {@code true} if the transition is overridden.
-     */
-    private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
-            ArraySet<Integer> activityTypes) {
-        if (transitionMayContainNonAppWindows(transit)) {
-            return false;
-        }
-        if (!transitionContainsTaskFragmentWithBoundsOverride()) {
-            // No need to play TaskFragment remote animation if all embedded TaskFragment in the
-            // transition fill the Task.
-            return false;
-        }
-
-        final Task task = findParentTaskForAllEmbeddedWindows();
-        final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
-        final RemoteAnimationDefinition definition = organizer != null
-                ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                    .getRemoteAnimationDefinition(organizer)
-                : null;
-        final RemoteAnimationAdapter adapter = definition != null
-                ? definition.getAdapter(transit, activityTypes)
-                : null;
-        if (adapter == null) {
-            return false;
-        }
-        mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
-                adapter, false /* sync */, true /*isActivityEmbedding*/);
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "Override with TaskFragment remote animation for transit=%s",
-                AppTransition.appTransitionOldToString(transit));
-
-        final int organizerUid = mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                .getTaskFragmentOrganizerUid(organizer);
-        final boolean shouldDisableInputForRemoteAnimation = !task.isFullyTrustedEmbedding(
-                organizerUid);
-        final RemoteAnimationController remoteAnimationController =
-                mDisplayContent.mAppTransition.getRemoteAnimationController();
-        if (shouldDisableInputForRemoteAnimation && remoteAnimationController != null) {
-            // We are going to use client-driven animation, Disable all input on activity windows
-            // during the animation (unless it is fully trusted) to ensure it is safe to allow
-            // client to animate the surfaces.
-            // This is needed for all activity windows in the animation Task.
-            remoteAnimationController.setOnRemoteAnimationReady(() -> {
-                final Consumer<ActivityRecord> updateActivities =
-                        activity -> activity.setDropInputForAnimation(true);
-                task.forAllActivities(updateActivities);
-            });
-            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment."
-                    + " Disabled all input during TaskFragment remote animation.", task.mTaskId);
-        }
-        return true;
-    }
-
-    /**
      * Overrides the pending transition with the remote animation defined for the transition in the
      * set of defined remote animations in the app window token.
      */
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/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index b6b6cf2..439c7bb 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -46,7 +46,6 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
@@ -58,8 +57,8 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
@@ -146,13 +145,6 @@
         private final boolean mIsSystemOrganizer;
 
         /**
-         * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
-         * organized by this organizer.
-         */
-        @Nullable
-        private RemoteAnimationDefinition mRemoteAnimationDefinition;
-
-        /**
          * Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
          * {@link Transition#getSyncId()} that has been deferred. {@link TransitionController} will
          * wait until the organizer finished handling the {@link TaskFragmentTransaction}.
@@ -533,50 +525,6 @@
     }
 
     @Override
-    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
-            @NonNull RemoteAnimationDefinition definition) {
-        final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
-        synchronized (mGlobalLock) {
-            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
-                    "Register remote animations for organizer=%s uid=%d pid=%d",
-                    organizer.asBinder(), uid, pid);
-            final TaskFragmentOrganizerState organizerState =
-                    mTaskFragmentOrganizerState.get(organizer.asBinder());
-            if (organizerState == null) {
-                throw new IllegalStateException("The organizer hasn't been registered.");
-            }
-            if (organizerState.mRemoteAnimationDefinition != null) {
-                throw new IllegalStateException(
-                        "The organizer has already registered remote animations="
-                                + organizerState.mRemoteAnimationDefinition);
-            }
-
-            definition.setCallingPidUid(pid, uid);
-            organizerState.mRemoteAnimationDefinition = definition;
-        }
-    }
-
-    @Override
-    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
-        final int pid = Binder.getCallingPid();
-        final long uid = Binder.getCallingUid();
-        synchronized (mGlobalLock) {
-            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
-                    "Unregister remote animations for organizer=%s uid=%d pid=%d",
-                    organizer.asBinder(), uid, pid);
-            final TaskFragmentOrganizerState organizerState =
-                    mTaskFragmentOrganizerState.get(organizer.asBinder());
-            if (organizerState == null) {
-                Slog.e(TAG, "The organizer hasn't been registered.");
-                return;
-            }
-
-            organizerState.mRemoteAnimationDefinition = null;
-        }
-    }
-
-    @Override
     public void onTransactionHandled(@NonNull IBinder transactionToken,
             @NonNull WindowContainerTransaction wct,
             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
@@ -617,25 +565,6 @@
         }
     }
 
-    /**
-     * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
-     * {@code null} if it doesn't.
-     */
-    @Nullable
-    public RemoteAnimationDefinition getRemoteAnimationDefinition(
-            @NonNull ITaskFragmentOrganizer organizer) {
-        synchronized (mGlobalLock) {
-            final TaskFragmentOrganizerState organizerState =
-                    mTaskFragmentOrganizerState.get(organizer.asBinder());
-            if (organizerState == null) {
-                Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
-                        + " to play animation on its organized windows.");
-                return null;
-            }
-            return organizerState.mRemoteAnimationDefinition;
-        }
-    }
-
     int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
         final TaskFragmentOrganizerState state = validateAndGetState(organizer);
         return state.mOrganizerUid;
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/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/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/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0c1fbf3..af4394a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,15 +25,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,7 +37,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,14 +46,11 @@
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.gui.DropInputMode;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -841,353 +833,6 @@
     }
 
     @Test
-    public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord activity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(activity);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
-        final Task task = createTask(mDisplayContent);
-        final ActivityRecord closingActivity = createActivityRecord(task);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
-        // Make sure the TaskFragment is not embedded.
-        assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        prepareActivityForAppTransition(openingActivity);
-        final int uid = 12345;
-        closingActivity.info.applicationInfo.uid = uid;
-        openingActivity.info.applicationInfo.uid = uid;
-        task.effectiveUid = uid;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity,
-                null /* changingTaskFragment */);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation is not run by the remote handler because the activity is filling the Task.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
-        final Task task = createTask(mDisplayContent);
-        final ActivityRecord closingActivity = createActivityRecord(task);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-
-        // Make sure the TaskFragment is embedded.
-        taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        final Rect embeddedBounds = new Rect(task.getBounds());
-        embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
-        taskFragment.setBounds(embeddedBounds);
-        assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        prepareActivityForAppTransition(openingActivity);
-        final int uid = 12345;
-        closingActivity.info.applicationInfo.uid = uid;
-        openingActivity.info.applicationInfo.uid = uid;
-        task.effectiveUid = uid;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity,
-                null /* changingTaskFragment */);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing non-embedded activity.
-        final ActivityRecord closingActivity = createActivityRecord(task);
-        prepareActivityForAppTransition(closingActivity);
-        // Opening TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(openingActivity);
-        task.effectiveUid = openingActivity.getUid();
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing TaskFragment with embedded activity.
-        final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        closingActivity.info.applicationInfo.uid = 12345;
-        // Opening TaskFragment with embedded activity with different UID.
-        final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
-        prepareActivityForAppTransition(openingActivity);
-        openingActivity.info.applicationInfo.uid = 54321;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation run by the remote handler.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing activity in Task1.
-        final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
-        prepareActivityForAppTransition(closingActivity);
-        // Opening TaskFragment with embedded activity in Task2.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(openingActivity);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation not run by the remote handler.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Closing TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(closingActivity);
-        closingActivity.info.applicationInfo.uid = 12345;
-        task.effectiveUid = closingActivity.getUid();
-        // Opening non-embedded activity with different UID.
-        final ActivityRecord openingActivity = createActivityRecord(task);
-        prepareActivityForAppTransition(openingActivity);
-        openingActivity.info.applicationInfo.uid = 54321;
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation should not run by the remote handler when there are non-embedded activities of
-        // different UID.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activity.
-        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
-        final ActivityRecord activity = taskFragment.getTopMostActivity();
-        prepareActivityForAppTransition(activity);
-        // Set wallpaper as visible.
-        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
-                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
-        spyOn(mDisplayContent.mWallpaperController);
-        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // Animation should not run by the remote handler when there is wallpaper in the transition.
-        assertFalse(remoteAnimationRunner.isAnimationStarted());
-    }
-
-    @Test
-    public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
-        // one is untrusted embedded.
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(2)
-                .setOrganizer(organizer)
-                .build();
-        final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord();
-        final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord();
-        // Also create a non-embedded activity in the Task.
-        final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
-        task.addChild(activity2, POSITION_BOTTOM);
-        prepareActivityForAppTransition(activity0);
-        prepareActivityForAppTransition(activity1);
-        prepareActivityForAppTransition(activity2);
-        doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0);
-        doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // The animation will be animated remotely by client and all activities are input disabled
-        // for untrusted animation.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-        verify(activity0).setDropInputForAnimation(true);
-        verify(activity1).setDropInputForAnimation(true);
-        verify(activity2).setDropInputForAnimation(true);
-        verify(activity0).setDropInputMode(DropInputMode.ALL);
-        verify(activity1).setDropInputMode(DropInputMode.ALL);
-        verify(activity2).setDropInputMode(DropInputMode.ALL);
-
-        // Reset input after animation is finished.
-        clearInvocations(activity0);
-        clearInvocations(activity1);
-        clearInvocations(activity2);
-        remoteAnimationRunner.finishAnimation();
-
-        verify(activity0).setDropInputForAnimation(false);
-        verify(activity1).setDropInputForAnimation(false);
-        verify(activity2).setDropInputForAnimation(false);
-        verify(activity0).setDropInputMode(DropInputMode.OBSCURED);
-        verify(activity1).setDropInputMode(DropInputMode.NONE);
-        verify(activity2).setDropInputMode(DropInputMode.NONE);
-    }
-
-    /**
-     * Since we don't have any use case to rely on handling input during animation, disable it even
-     * if it is trusted embedding so that it could cover some edge-cases when a previously trusted
-     * host starts doing something bad.
-     */
-    @Test
-    public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with only trusted embedded activity
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(1)
-                .setOrganizer(organizer)
-                .build();
-        final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
-        prepareActivityForAppTransition(activity);
-        doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // The animation will be animated remotely by client and all activities are input disabled
-        // for untrusted animation.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-        verify(activity).setDropInputForAnimation(true);
-        verify(activity).setDropInputMode(DropInputMode.ALL);
-
-        // Reset input after animation is finished.
-        clearInvocations(activity);
-        remoteAnimationRunner.finishAnimation();
-
-        verify(activity).setDropInputForAnimation(false);
-        verify(activity).setDropInputMode(DropInputMode.NONE);
-    }
-
-    /**
-     * We don't need to drop input for fully trusted embedding (system app, and embedding in the
-     * same app). This will allow users to do fast tapping.
-     */
-    @Test
-    public void testOverrideTaskFragmentAdapter_noInputProtectedForFullyTrustedAnimation() {
-        final Task task = createTask(mDisplayContent);
-        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
-        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
-
-        // Create a TaskFragment with only trusted embedded activity
-        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(task)
-                .createActivityCount(1)
-                .setOrganizer(organizer)
-                .build();
-        final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
-        prepareActivityForAppTransition(activity);
-        final int uid = mAtm.mTaskFragmentOrganizerController.getTaskFragmentOrganizerUid(
-                getITaskFragmentOrganizer(organizer));
-        doReturn(true).when(task).isFullyTrustedEmbedding(uid);
-        spyOn(mDisplayContent.mAppTransition);
-
-        // Prepare and start transition.
-        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
-        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-        // The animation will be animated remotely by client, but input should not be dropped for
-        // fully trusted.
-        assertTrue(remoteAnimationRunner.isAnimationStarted());
-        verify(activity, never()).setDropInputForAnimation(true);
-        verify(activity, never()).setDropInputMode(DropInputMode.ALL);
-    }
-
-    @Test
     public void testTransitionGoodToGoForTaskFragments() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final Task task = createTask(mDisplayContent);
@@ -1253,22 +898,6 @@
         verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
     }
 
-    /** Registers remote animation for the organizer. */
-    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
-            TestRemoteAnimationRunner remoteAnimationRunner) {
-        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
-                remoteAnimationRunner, 10, 1);
-        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
-        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
-        registerTaskFragmentOrganizer(iOrganizer);
-        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
-    }
-
     private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
             TaskFragmentOrganizer organizer) {
         return ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
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 5fe8524..ae88b1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -104,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;
@@ -495,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
@@ -515,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();
@@ -555,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();
 
@@ -693,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();
 
@@ -752,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();
@@ -849,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();
 
@@ -1624,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.
@@ -1659,7 +1739,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
@@ -2000,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);
@@ -2775,7 +2855,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertEquals(activityBounds.width(), newActivityBounds.width());
         assertEquals(activityBounds.height(), newActivityBounds.height());
         assertActivityMaxBoundsSandboxed();
@@ -2807,7 +2887,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
@@ -2955,7 +3035,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         // Activity max bounds are sandboxed due to size compat mode.
         assertActivityMaxBoundsSandboxed();
@@ -2967,7 +3047,7 @@
         verify(mActivity, never()).clearSizeCompatMode();
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertEquals(activityBounds, mActivity.getBounds());
         // Activity max bounds are sandboxed due to size compat.
         assertActivityMaxBoundsSandboxed();
@@ -2995,7 +3075,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to landscape.
@@ -3032,7 +3112,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to portrait.
@@ -3224,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());
@@ -3288,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());
@@ -3329,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();
@@ -3906,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());
     }
 
@@ -4406,7 +4486,7 @@
         resizeDisplay(mTask.mDisplayContent, 1400, 700);
 
         assertTrue(mActivity.inSizeCompatMode());
-        assertScaled();
+        assertDownScaled();
         assertEquals(sizeCompatScaled, mActivity.getBounds());
     }
 
@@ -4672,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();
@@ -4686,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)
@@ -4719,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.
@@ -4958,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/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a71b81e..d013053 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -90,7 +90,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.window.IRemoteTransition;
 import android.window.ITaskFragmentOrganizer;
@@ -140,7 +139,6 @@
     private IBinder mFragmentToken;
     private WindowContainerTransaction mTransaction;
     private WindowContainerToken mFragmentWindowToken;
-    private RemoteAnimationDefinition mDefinition;
     private IBinder mErrorToken;
     private Rect mTaskFragBounds;
 
@@ -169,7 +167,6 @@
         mTransaction = new WindowContainerTransaction();
         mTransaction.setTaskFragmentOrganizer(mIOrganizer);
         mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
-        mDefinition = new RemoteAnimationDefinition();
         mErrorToken = new Binder();
         final Rect displayBounds = mDisplayContent.getBounds();
         mTaskFragBounds = new Rect(displayBounds.left, displayBounds.top, displayBounds.centerX(),
@@ -579,17 +576,6 @@
     }
 
     @Test
-    public void testRegisterRemoteAnimations() {
-        mController.registerRemoteAnimations(mIOrganizer, mDefinition);
-
-        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
-
-        mController.unregisterRemoteAnimations(mIOrganizer);
-
-        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
-    }
-
-    @Test
     public void testApplyTransaction_disallowRemoteTransitionForNonSystemOrganizer() {
         mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
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