Merge "[Autofill Framework] Add some verbose logging to Session when it's triggering fill request." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 1667f2e..5d134c6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -33228,6 +33228,7 @@
   }
 
   public interface IBinder {
+    method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException;
     method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
     method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
     method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
@@ -33236,6 +33237,7 @@
     method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
     method public boolean pingBinder();
     method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String);
+    method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default boolean removeFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback);
     method public boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException;
     method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int);
     field public static final int DUMP_TRANSACTION = 1598311760; // 0x5f444d50
@@ -33253,6 +33255,12 @@
     method public default void binderDied(@NonNull android.os.IBinder);
   }
 
+  @FlaggedApi("android.os.binder_frozen_state_change_callback") public static interface IBinder.FrozenStateChangeCallback {
+    method public void onFrozenStateChanged(@NonNull android.os.IBinder, int);
+    field public static final int STATE_FROZEN = 0; // 0x0
+    field public static final int STATE_UNFROZEN = 1; // 0x1
+  }
+
   public interface IInterface {
     method public android.os.IBinder asBinder();
   }
@@ -44070,7 +44078,7 @@
   }
 
   public static final class CarrierConfigManager.Gps {
-    field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool";
+    field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config_bugfix") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool";
     field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
     field public static final String KEY_PREFIX = "gps.";
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4350545..5db79fe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -8288,12 +8288,12 @@
             }
             Context c = null;
             ApplicationInfo ai = info.applicationInfo;
-            if (context.getPackageName().equals(ai.packageName)) {
+            if (context != null && context.getPackageName().equals(ai.packageName)) {
                 c = context;
             } else if (mInitialApplication != null &&
                     mInitialApplication.getPackageName().equals(ai.packageName)) {
                 c = mInitialApplication;
-            } else {
+            } else if (context != null) {
                 try {
                     c = context.createPackageContext(ai.packageName,
                             Context.CONTEXT_INCLUDE_CODE);
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 9891e89..9b06adf 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -236,4 +236,12 @@
   namespace: "systemui"
   description: "Guards new android.app.richongoingnotification api"
   bug: "337261753"
+}
+
+flag {
+  name: "ui_rich_ongoing"
+  is_exported: true
+  namespace: "systemui"
+  description: "Guards new android.app.richongoingnotification promotion and new uis"
+  bug: "337261753"
 }
\ No newline at end of file
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index c22f46c..80546cd 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -650,13 +650,13 @@
      * weakly referenced by JNI so the strong references here are needed to keep the callbacks
      * around until the proxy is GC'ed.
      */
-    private List<IFrozenStateChangeCallback> mFrozenStateChangeCallbacks =
+    private List<FrozenStateChangeCallback> mFrozenStateChangeCallbacks =
             Collections.synchronizedList(new ArrayList<>());
 
     /**
-     * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)}
+     * See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)}
      */
-    public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback)
+    public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback)
             throws RemoteException {
         addFrozenStateChangeCallbackNative(callback);
         mFrozenStateChangeCallbacks.add(callback);
@@ -665,16 +665,16 @@
     /**
      * See {@link IBinder#removeFrozenStateChangeCallback}
      */
-    public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) {
+    public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) {
         mFrozenStateChangeCallbacks.remove(callback);
         return removeFrozenStateChangeCallbackNative(callback);
     }
 
-    private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback)
+    private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback)
             throws RemoteException;
 
     private native boolean removeFrozenStateChangeCallbackNative(
-            IFrozenStateChangeCallback callback);
+            FrozenStateChangeCallback callback);
 
     /**
      * Perform a dump on the remote object
@@ -762,10 +762,9 @@
     }
 
     private static void invokeFrozenStateChangeCallback(
-            IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) {
+            FrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) {
         try {
-            callback.onFrozenStateChanged(binderProxy,
-                    IFrozenStateChangeCallback.State.values()[stateIndex]);
+            callback.onFrozenStateChanged(binderProxy, stateIndex);
         } catch (RuntimeException exc) {
             Log.w("BinderNative", "Uncaught exception from frozen state change callback",
                     exc);
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 8185e8e..a997f4c 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -16,11 +16,15 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 
 import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Base interface for a remotable object, the core part of a lightweight
@@ -377,9 +381,24 @@
      */
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
 
-    /** @hide */
-    interface IFrozenStateChangeCallback {
-        enum State {FROZEN, UNFROZEN};
+    /**
+     * A callback interface for receiving frozen state change events.
+     */
+    @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+    interface FrozenStateChangeCallback {
+        /**
+         * @hide
+         */
+        @IntDef(prefix = {"STATE_"}, value = {
+                STATE_FROZEN,
+                STATE_UNFROZEN,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface State {
+        }
+
+        int STATE_FROZEN = 0;
+        int STATE_UNFROZEN = 1;
 
         /**
          * Interface for receiving a callback when the process hosting an IBinder
@@ -387,13 +406,13 @@
          * @param who The IBinder whose hosting process has changed state.
          * @param state The latest state.
          */
-        void onFrozenStateChanged(@NonNull IBinder who, State state);
+        void onFrozenStateChanged(@NonNull IBinder who, @State int state);
     }
 
     /**
-     * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process
-     * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is
-     * invoked with the latest process frozen state.
+     * This method provides a callback mechanism to notify about process frozen/unfrozen events.
+     * Upon registration and any subsequent state changes, the callback is invoked with the latest
+     * process frozen state.
      *
      * <p>If the listener process (the one using this API) is itself frozen, state change events
      * might be combined into a single one with the latest frozen state. This single event would
@@ -410,19 +429,19 @@
      *
      * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support
      * this feature.
-     * @hide
      */
-    default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback)
+    @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+    default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback)
             throws RemoteException {
         throw new UnsupportedOperationException();
     }
 
     /**
-     * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when
+     * Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when
      * the hosting process changes its frozen state.
-     * @hide
      */
-    default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) {
+    @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+    default boolean removeFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) {
         throw new UnsupportedOperationException();
     }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a4a7a98..1ca4574 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3763,7 +3763,8 @@
     }
 
     private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
-            "cache_key.is_user_unlocked";
+        PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
 
     private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
             new PropertyInvalidatedCache<Integer, Boolean>(
@@ -6694,7 +6695,9 @@
     }
 
     /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
-    private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
+    private static final String CACHE_KEY_STATIC_USER_PROPERTIES =
+        PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "static_user_props");
 
     private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache =
             new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) {
@@ -6721,7 +6724,9 @@
     }
 
     /* Cache key for UserProperties object. */
-    private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+    private static final String CACHE_KEY_USER_PROPERTIES =
+        PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "user_properties");
 
     // TODO: It would be better to somehow have this as static, so that it can work cross-context.
     private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 39bd15c..738d129 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -153,6 +153,14 @@
 }
 
 flag {
+    name: "binder_frozen_state_change_callback"
+    is_exported: true
+    namespace: "system_performance"
+    description: "Guards the frozen state change callback API."
+    bug: "361157077"
+}
+
+flag {
     name: "message_queue_tail_tracking"
     namespace: "system_performance"
     description: "track tail of message queue."
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e32625e..0ada993 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -111,6 +111,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -3022,6 +3023,9 @@
     /** @hide - Private call() method to query the 'configuration' table */
     public static final String CALL_METHOD_LIST_CONFIG = "LIST_config";
 
+    /** @hide - Private call() method to query the 'configuration' tables' namespaces */
+    public static final String CALL_METHOD_LIST_NAMESPACES_CONFIG = "LIST_namespaces_config";
+
     /** @hide - Private call() method to disable / re-enable syncs to the 'configuration' table */
     public static final String CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG =
             "SET_SYNC_DISABLED_MODE_config";
@@ -20458,6 +20462,10 @@
          *
          * The keys take the form {@code namespace/flag}, and the values are the flag values.
          *
+         * Note: this API is _not_ performant, and may make a large number of
+         * Binder calls. It is intended for use in testing and debugging, and
+         * should not be used in performance-sensitive code.
+         *
          * @hide
          */
         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -20469,13 +20477,33 @@
                 Bundle arg = new Bundle();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, resolver.getUserId());
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
-                Bundle b = cp.call(resolver.getAttributionSource(),
-                        sProviderHolder.mUri.getAuthority(), CALL_METHOD_LIST_CONFIG, null, arg);
-                if (b != null) {
-                    Map<String, String> flagsToValues =
-                            (HashMap) b.getSerializable(Settings.NameValueTable.VALUE,
-                                            java.util.HashMap.class);
-                    allFlags.putAll(flagsToValues);
+
+                if (Flags.reduceBinderTransactionSizeForGetAllProperties()) {
+                    Bundle b = cp.call(resolver.getAttributionSource(),
+                            sProviderHolder.mUri.getAuthority(),
+                                CALL_METHOD_LIST_NAMESPACES_CONFIG, null, arg);
+                    if (b != null) {
+                        HashSet<String> namespaces =
+                                (HashSet) b.getSerializable(Settings.NameValueTable.VALUE,
+                                                java.util.HashSet.class);
+                        for (String namespace : namespaces) {
+                            Map<String, String> keyValues =
+                                    getStrings(namespace, new ArrayList());
+                            for (String key : keyValues.keySet()) {
+                                allFlags.put(namespace + "/" + key, keyValues.get(key));
+                            }
+                        }
+                    }
+                } else {
+                    Bundle b = cp.call(resolver.getAttributionSource(),
+                            sProviderHolder.mUri.getAuthority(),
+                            CALL_METHOD_LIST_CONFIG, null, arg);
+                    if (b != null) {
+                        Map<String, String> flagsToValues =
+                                (HashMap) b.getSerializable(Settings.NameValueTable.VALUE,
+                                                java.util.HashMap.class);
+                        allFlags.putAll(flagsToValues);
+                    }
                 }
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't query configuration table for " + CONTENT_URI, e);
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 5c0f873..4c63673 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -52,3 +52,14 @@
     description: "Enable the new ContactsContract Default Account APIs."
     bug: "359957527"
 }
+
+flag {
+    name: "reduce_binder_transaction_size_for_get_all_properties"
+    namespace: "core_experiments_team_internal"
+    description: "Reduce Binder transaction size in getAllProperties calls"
+    bug: "362652574"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index ad457ce..384add5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -70,6 +70,7 @@
 import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -370,6 +371,7 @@
         private float mDefaultDimAmount = 0.05f;
         SurfaceControl mBbqSurfaceControl;
         BLASTBufferQueue mBlastBufferQueue;
+        IBinder mBbqApplyToken = new Binder();
         private SurfaceControl mScreenshotSurfaceControl;
         private Point mScreenshotSize = new Point();
 
@@ -2390,6 +2392,7 @@
             if (mBlastBufferQueue == null) {
                 mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl,
                         width, height, format);
+                mBlastBufferQueue.setApplyToken(mBbqApplyToken);
                 // We only return the Surface the first time, as otherwise
                 // it hasn't changed and there is no need to update.
                 ret = mBlastBufferQueue.createSurface();
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3c61f4f..3846972 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -84,17 +84,6 @@
 }
 
 flag {
-  name: "fix_font_update_failure"
-  namespace: "text"
-  description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
-  is_fixed_read_only: true
-  bug: "331717791"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "fix_misaligned_context_menu"
   namespace: "text"
   description: "Fix the context menu misalignment and incosistent icon size."
@@ -154,26 +143,6 @@
 }
 
 flag {
-  name: "portuguese_hyphenator"
-  namespace: "text"
-  description: "Portuguese taiored hyphenator"
-  bug: "344656282"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
-  name: "dont_break_email_in_nobreak_tag"
-  namespace: "text"
-  description: "Prevent line break inside email."
-  bug: "350691716"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "handwriting_gesture_with_transformation"
   namespace: "text"
   description: "Fix handwriting gesture is not working when view has transformation."
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 71ceaf7..53935e8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1341,7 +1341,7 @@
     public HdrCapabilities getHdrCapabilities() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            if (mDisplayInfo.hdrCapabilities == null) {
+            if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) {
                 return null;
             }
             int[] supportedHdrTypes;
@@ -1363,6 +1363,7 @@
                     supportedHdrTypes[index++] = enabledType;
                 }
             }
+
             return new HdrCapabilities(supportedHdrTypes,
                     mDisplayInfo.hdrCapabilities.mMaxLuminance,
                     mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 157cec8..cac3e3c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -230,6 +230,9 @@
     /** The formats disabled by user **/
     public int[] userDisabledHdrTypes = {};
 
+    /** When true, all HDR capabilities are disabled **/
+    public boolean isForceSdr;
+
     /**
      * Indicates whether the display can be switched into a mode with minimal post
      * processing.
@@ -440,6 +443,7 @@
                 && colorMode == other.colorMode
                 && Arrays.equals(supportedColorModes, other.supportedColorModes)
                 && Objects.equals(hdrCapabilities, other.hdrCapabilities)
+                && isForceSdr == other.isForceSdr
                 && Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes)
                 && minimalPostProcessingSupported == other.minimalPostProcessingSupported
                 && logicalDensityDpi == other.logicalDensityDpi
@@ -502,6 +506,7 @@
         supportedColorModes = Arrays.copyOf(
                 other.supportedColorModes, other.supportedColorModes.length);
         hdrCapabilities = other.hdrCapabilities;
+        isForceSdr = other.isForceSdr;
         userDisabledHdrTypes = other.userDisabledHdrTypes;
         minimalPostProcessingSupported = other.minimalPostProcessingSupported;
         logicalDensityDpi = other.logicalDensityDpi;
@@ -567,6 +572,7 @@
             supportedColorModes[i] = source.readInt();
         }
         hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
+        isForceSdr = source.readBoolean();
         minimalPostProcessingSupported = source.readBoolean();
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
@@ -636,6 +642,7 @@
             dest.writeInt(supportedColorModes[i]);
         }
         dest.writeParcelable(hdrCapabilities, flags);
+        dest.writeBoolean(isForceSdr);
         dest.writeBoolean(minimalPostProcessingSupported);
         dest.writeInt(logicalDensityDpi);
         dest.writeFloat(physicalXDpi);
@@ -874,6 +881,8 @@
         sb.append(Arrays.toString(appsSupportedModes));
         sb.append(", hdrCapabilities ");
         sb.append(hdrCapabilities);
+        sb.append(", isForceSdr ");
+        sb.append(isForceSdr);
         sb.append(", userDisabledHdrTypes ");
         sb.append(Arrays.toString(userDisabledHdrTypes));
         sb.append(", minimalPostProcessingSupported ");
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 6b4340a..15a4715 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -38,6 +38,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -55,7 +56,6 @@
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -146,7 +146,7 @@
                 forceConsumingTypes |= type;
             }
 
-            if (Flags.enableCaptionCompatInsetForceConsumptionAlways()
+            if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()
                     && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
                 forceConsumingOpaqueCaptionBar = true;
             }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e10cc28..33e7905 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -125,11 +125,11 @@
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
 import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
-import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption;
 import static com.android.window.flags.Flags.insetsControlChangedItem;
 import static com.android.window.flags.Flags.insetsControlSeq;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -829,6 +829,7 @@
     private final SurfaceControl mSurfaceControl = new SurfaceControl();
 
     private BLASTBufferQueue mBlastBufferQueue;
+    private IBinder mBbqApplyToken = new Binder();
 
     private final HdrRenderState mHdrRenderState = new HdrRenderState(this);
 
@@ -2743,6 +2744,10 @@
         mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
                 mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
         mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
+        // If we create and destroy BBQ without recreating the SurfaceControl, we can end up
+        // queuing buffers on multiple apply tokens causing out of order buffer submissions. We
+        // fix this by setting the same apply token on all BBQs created by this VRI.
+        mBlastBufferQueue.setApplyToken(mBbqApplyToken);
         Surface blastSurface;
         if (addSchandleToVriSurface()) {
             blastSurface = mBlastBufferQueue.createSurfaceWithHandle();
@@ -3209,10 +3214,10 @@
             typesToShow |= Type.navigationBars();
         }
         if (captionIsHiddenByFlags && !captionWasHiddenByFlags
-                && enableCaptionCompatInsetForceConsumption()) {
+                && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
             typesToHide |= Type.captionBar();
         } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags
-                && enableCaptionCompatInsetForceConsumption()) {
+                && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
             typesToShow |= Type.captionBar();
         }
         if (typesToHide != 0) {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index b9751c8..d90455a 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -920,7 +920,8 @@
             final Configuration.Builder builder = Configuration.Builder.withSurface(
                             cujType,
                             jankContext.getDisplayContext(),
-                            jankContext.getTargetSurfaceControl())
+                            jankContext.getTargetSurfaceControl(),
+                            jankContext.getDisplayContext().getMainThreadHandler())
                     .setTag(String.format(Locale.US, "%d@%d@%s", animType,
                             useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
             InteractionJankMonitor.getInstance().begin(builder);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e1154ca..06820cd 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1003,6 +1003,55 @@
         public int getActionTag() {
             return SET_EMPTY_VIEW_ACTION_TAG;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION);
+            out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID:
+                        values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID,
+                                in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID:
+                        values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID,
+                                in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID,
+                    RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetEmptyViewAction.VIEW_ID);
+                int emptyViewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID);
+                return new SetEmptyView(viewId, emptyViewId);
+            };
+        }
     }
 
     private static class SetPendingIntentTemplate extends Action {
@@ -1243,6 +1292,68 @@
 
             mItems.visitUris(visitor);
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            // Skip actions that do not contain items (intent only actions)
+            return mItems != null;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            if (mItems == null) return;
+            final long token = out.start(
+                    RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION);
+            out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            final long itemsToken = out.start(
+                    RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS);
+            mItems.writeToProto(context, out, /* attached= */ true);
+            out.end(itemsToken);
+            out.end(token);
+        }
+    }
+
+    private PendingResources<Action> createSetRemoteCollectionItemListAdapterActionFromProto(
+            ProtoInputStream in) throws Exception {
+        final LongSparseArray<Object> values = new LongSparseArray<>();
+
+        final long token = in.start(
+                RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION);
+        while (in.nextField() != NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID:
+                    values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID,
+                            in.readString(
+                                    RemoteViewsProto
+                                            .SetRemoteCollectionItemListAdapterAction.VIEW_ID));
+                    break;
+                case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS:
+                    final long itemsToken = in.start(
+                            RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS);
+                    values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS,
+                            RemoteCollectionItems.createFromProto(in));
+                    in.end(itemsToken);
+                    break;
+                default:
+                    Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        in.end(token);
+
+        checkContainsKeys(values,
+                new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID,
+                        RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS});
+
+        return (context, resources, rootData, depth) -> {
+            int viewId = getAsIdentifier(resources, values,
+                    RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID);
+            return new SetRemoteCollectionItemListAdapterAction(viewId,
+                    ((PendingResources<RemoteCollectionItems>) values.get(
+                            RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS))
+                            .create(context, resources, rootData, depth));
+        };
     }
 
     /**
@@ -2036,6 +2147,68 @@
         public int getActionTag() {
             return SET_DRAWABLE_TINT_TAG;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION);
+            out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter);
+            out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE,
+                    PorterDuff.modeToInt(mFilterMode));
+            out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground);
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID:
+                        values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID,
+                                in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND:
+                        values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND,
+                                in.readBoolean(
+                                        RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND));
+                        break;
+                    case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER:
+                        values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER,
+                                in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER));
+                        break;
+                    case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE:
+                        values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE,
+                                PorterDuff.intToMode(in.readInt(
+                                        RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetDrawableTintAction.VIEW_ID);
+                return new SetDrawableTint(viewId, (boolean) values.get(
+                        RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false),
+                        (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0),
+                        (PorterDuff.Mode) values.get(
+                                RemoteViewsProto.SetDrawableTintAction.FILTER_MODE));
+            };
+        }
     }
 
     /**
@@ -2047,7 +2220,7 @@
      * target {@link View#getBackground()}.
      * <p>
      */
-    private class SetRippleDrawableColor extends Action {
+    private static class SetRippleDrawableColor extends Action {
         ColorStateList mColorStateList;
 
         SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) {
@@ -2082,6 +2255,58 @@
         public int getActionTag() {
             return SET_RIPPLE_DRAWABLE_COLOR_TAG;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION);
+            out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            writeColorStateListToProto(out, mColorStateList,
+                    RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST);
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID:
+                        values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID,
+                                in.readString(
+                                        RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST:
+                        values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST,
+                                createColorStateListFromProto(in,
+                                        RemoteViewsProto
+                                                .SetRippleDrawableColorAction.COLOR_STATE_LIST));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values,
+                    new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID,
+                            RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID);
+                return new SetRippleDrawableColor(viewId, (ColorStateList) values.get(
+                        RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST));
+            };
+        }
     }
 
     /**
@@ -2987,6 +3212,82 @@
         public int getActionTag() {
             return RESOURCE_REFLECTION_ACTION_TAG;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION);
+            out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName);
+            out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType);
+            out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType);
+            if (mResId != 0) {
+                out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID,
+                        appResources.getResourceName(mResId));
+            }
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID:
+                        values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID,
+                                in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME:
+                        values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME,
+                                in.readString(
+                                        RemoteViewsProto.ResourceReflectionAction.METHOD_NAME));
+                        break;
+                    case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE:
+                        values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE,
+                                in.readInt(
+                                        RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE));
+                        break;
+                    case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID:
+                        values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID,
+                                in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID));
+                        break;
+                    case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE:
+                        values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE,
+                                in.readInt(
+                                        RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID,
+                    RemoteViewsProto.ResourceReflectionAction.METHOD_NAME,
+                    RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.ResourceReflectionAction.VIEW_ID);
+
+                int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID)
+                        >= 0) ? getAsIdentifier(resources, values,
+                        RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0;
+                return new ResourceReflectionAction(viewId,
+                        (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME),
+                        (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE),
+                        (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE,
+                                0), resId);
+            };
+        }
     }
 
     private static final class AttributeReflectionAction extends BaseReflectionAction {
@@ -4593,6 +4894,61 @@
         public int getActionTag() {
             return SET_INT_TAG_TAG;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION);
+            out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            out.write(RemoteViewsProto.SetIntTagAction.KEY,
+                    appResources.getResourceName(mKey)); // rebase
+            out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag);
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID:
+                        values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID,
+                                in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.SetIntTagAction.KEY:
+                        values.put(RemoteViewsProto.SetIntTagAction.KEY,
+                                in.readString(RemoteViewsProto.SetIntTagAction.KEY));
+                        break;
+                    case (int) RemoteViewsProto.SetIntTagAction.TAG:
+                        values.put(RemoteViewsProto.SetIntTagAction.TAG,
+                                in.readInt(RemoteViewsProto.SetIntTagAction.TAG));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID,
+                    RemoteViewsProto.SetIntTagAction.KEY});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetIntTagAction.VIEW_ID);
+                int keyId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetIntTagAction.KEY);
+                return new SetIntTagAction(viewId, keyId,
+                        (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0));
+            };
+        }
     }
 
     private static class SetCompoundButtonCheckedAction extends Action {
@@ -4643,6 +4999,56 @@
         public int getActionTag() {
             return SET_COMPOUND_BUTTON_CHECKED_TAG;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(
+                    RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION);
+            out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked);
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID:
+                        values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID,
+                                in.readString(
+                                        RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED:
+                        values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED,
+                                in.readBoolean(
+                                        RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values,
+                    new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID);
+                return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get(
+                        RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false));
+            };
+        }
     }
 
     private static class SetRadioGroupCheckedAction extends Action {
@@ -4707,6 +5113,61 @@
         public int getActionTag() {
             return SET_RADIO_GROUP_CHECKED;
         }
+
+        @Override
+        public boolean canWriteToProto() {
+            return true;
+        }
+
+        @Override
+        public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+            final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION);
+            out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID,
+                    appResources.getResourceName(mViewId));
+            if (mCheckedId != -1) {
+                out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID,
+                        appResources.getResourceName(mCheckedId));
+            }
+            out.end(token);
+        }
+
+        public static PendingResources<Action> createFromProto(ProtoInputStream in)
+                throws Exception {
+            final LongSparseArray<Object> values = new LongSparseArray<>();
+
+            final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION);
+            while (in.nextField() != NO_MORE_FIELDS) {
+                switch (in.getFieldNumber()) {
+                    case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID:
+                        values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID,
+                                in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID));
+                        break;
+                    case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID:
+                        values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID,
+                                in.readString(
+                                        RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID));
+                        break;
+                    default:
+                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+                                + ProtoUtils.currentFieldToString(in));
+                }
+            }
+            in.end(token);
+
+            checkContainsKeys(values,
+                    new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID});
+
+            return (context, resources, rootData, depth) -> {
+                int viewId = getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID);
+
+                int checkedId = (values.indexOfKey(
+                        RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0)
+                        ? getAsIdentifier(resources, values,
+                        RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1;
+                return new SetRadioGroupCheckedAction(viewId, checkedId);
+            };
+        }
     }
 
     private static class SetViewOutlinePreferredRadiusAction extends Action {
@@ -8450,6 +8911,7 @@
         public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in)
                 throws Exception {
             final LongSparseArray<Object> values = new LongSparseArray<>();
+
             values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>());
             values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS,
                     new ArrayList<PendingResources<RemoteViews>>());
@@ -9207,6 +9669,22 @@
                 return ReflectionAction.createFromProto(in);
             case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION:
                 return RemoveFromParentAction.createFromProto(in);
+            case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION:
+                return ResourceReflectionAction.createFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION:
+                return SetCompoundButtonCheckedAction.createFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION:
+                return SetDrawableTint.createFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION:
+                return SetEmptyView.createFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION:
+                return SetIntTagAction.createFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION:
+                return SetRadioGroupCheckedAction.createFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION:
+                return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in);
+            case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION:
+                return SetRippleDrawableColor.createFromProto(in);
             default:
                 throw new RuntimeException("Unhandled field while reading Action proto!\n"
                         + ProtoUtils.currentFieldToString(in));
diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java
index 5c53d66..701b6be 100644
--- a/core/java/android/window/flags/DesktopModeFlags.java
+++ b/core/java/android/window/flags/DesktopModeFlags.java
@@ -17,7 +17,9 @@
 package android.window.flags;
 
 import android.annotation.Nullable;
-import android.content.Context;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.content.ContentResolver;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -39,9 +41,13 @@
  */
 public enum DesktopModeFlags {
     // All desktop mode related flags to be overridden by developer option toggle will be added here
-    DESKTOP_WINDOWING_MODE(
+    ENABLE_DESKTOP_WINDOWING_MODE(
             Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
-    DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false);
+    ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false),
+    ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(
+            Flags::enableCaptionCompatInsetForceConsumption, true),
+    ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
+            Flags::enableCaptionCompatInsetForceConsumptionAlways, true);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
@@ -62,14 +68,15 @@
      * Determines state of flag based on the actual flag and desktop mode developer option
      * overrides.
      */
-    public boolean isEnabled(Context context) {
+    public boolean isEnabled() {
+        Application application = ActivityThread.currentApplication();
         if (!Flags.showDesktopWindowingDevOption()
                 || !mShouldOverrideByDevOption
-                || context.getContentResolver() == null) {
+                || application == null) {
             return mFlagFunction.get();
         } else {
             boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
-            return switch (getToggleOverride(context)) {
+            return switch (getToggleOverride(application.getContentResolver())) {
                 case OVERRIDE_UNSET -> mFlagFunction.get();
                 // When toggle override matches its default state, don't override flags. This
                 // helps users reset their feature overrides.
@@ -79,14 +86,14 @@
         }
     }
 
-    private ToggleOverride getToggleOverride(Context context) {
+    private ToggleOverride getToggleOverride(ContentResolver contentResolver) {
         // If cached, return it
         if (sCachedToggleOverride != null) {
             return sCachedToggleOverride;
         }
 
         // Otherwise, fetch and cache it
-        ToggleOverride override = getToggleOverrideFromSystem(context);
+        ToggleOverride override = getToggleOverrideFromSystem(contentResolver);
         sCachedToggleOverride = override;
         Log.d(TAG, "Toggle override initialized to: " + override);
         return override;
@@ -95,9 +102,9 @@
     /**
      *  Returns {@link ToggleOverride} from Settings.Global set by toggle.
      */
-    private ToggleOverride getToggleOverrideFromSystem(Context context) {
+    private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) {
         int settingValue = Settings.Global.getInt(
-                context.getContentResolver(),
+                contentResolver,
                 Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
                 ToggleOverride.OVERRIDE_UNSET.getSetting()
         );
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index c7e1fba..ef08e49 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -330,9 +330,10 @@
      * @param cujType the specific {@link Cuj.CujType}.
      * @return boolean true if the tracker is started successfully, false otherwise.
      */
-    public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType) {
+    public boolean begin(SurfaceControl surface, Context context, Handler handler,
+            @Cuj.CujType int cujType) {
         try {
-            return begin(Configuration.Builder.withSurface(cujType, context, surface));
+            return begin(Configuration.Builder.withSurface(cujType, context, surface, handler));
         } catch (IllegalArgumentException ex) {
             Log.d(TAG, "Build configuration failed!", ex);
             return false;
@@ -348,11 +349,12 @@
      * @param tag a tag containing extra information about the interaction.
      * @return boolean true if the tracker is started successfully, false otherwise.
      */
-    public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType,
+    public boolean begin(SurfaceControl surface, Context context, Handler handler,
+            @Cuj.CujType int cujType,
             String tag) {
         try {
             final Configuration.Builder builder =
-                    Configuration.Builder.withSurface(cujType, context, surface);
+                    Configuration.Builder.withSurface(cujType, context, surface, handler);
             if (!TextUtils.isEmpty(tag)) {
                 builder.setTag(tag);
             }
@@ -689,20 +691,23 @@
             private SurfaceControl mAttrSurfaceControl;
             private final @Cuj.CujType int mAttrCujType;
             private boolean mAttrDeferMonitor = true;
+            private Handler mHandler = null;
 
             /**
              * Creates a builder which instruments only surface.
              * @param cuj The enum defined in {@link Cuj.CujType}.
              * @param context context
              * @param surfaceControl surface control
+             * @param uiThreadHandler UI thread for that surface
              * @return builder
              */
             public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
-                    @NonNull SurfaceControl surfaceControl) {
+                    @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler) {
                 return new Builder(cuj)
                         .setContext(context)
                         .setSurfaceControl(surfaceControl)
-                        .setSurfaceOnly(true);
+                        .setSurfaceOnly(true)
+                        .setHandler(uiThreadHandler);
             }
 
             /**
@@ -722,6 +727,18 @@
             }
 
             /**
+             * Specifies the UI thread handler. If not provided, the View's one will be used.
+             * If only a surface is provided without handler, the app main thread will be used.
+             *
+             * @param uiThreadHandler handler associated to the cuj UI thread
+             * @return builder
+             */
+            public Builder setHandler(Handler uiThreadHandler) {
+                mHandler = uiThreadHandler;
+                return this;
+            }
+
+            /**
              * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
              * @param view an attached view
              * @return builder
@@ -798,13 +815,13 @@
                 return new Configuration(
                         mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
                         mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl,
-                        mAttrDeferMonitor);
+                        mAttrDeferMonitor, mHandler);
             }
         }
 
         private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
                 boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
-                boolean deferMonitor) {
+                boolean deferMonitor, Handler handler) {
             mCujType = cuj;
             mTag = tag;
             mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
@@ -816,8 +833,16 @@
                     : (view != null ? view.getContext().getApplicationContext() : null);
             mSurfaceControl = surfaceControl;
             mDeferMonitor = deferMonitor;
+            if (handler != null) {
+                mHandler = handler;
+            } else if (mSurfaceOnly) {
+                Log.w(TAG, "No UIThread provided for " + mSessionName
+                        + " (surface only). Defaulting to app main thread.");
+                mHandler = mContext.getMainThreadHandler();
+            } else {
+                mHandler = mView.getHandler();
+            }
             validate();
-            mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
         }
 
         @VisibleForTesting
@@ -858,6 +883,12 @@
                     shouldThrow = true;
                     msg.append("Must pass in a valid surface control if only instrument surface; ");
                 }
+                if (mHandler == null) {
+                    shouldThrow = true;
+                    msg.append(
+                            "Must pass a UI thread handler when only a surface control is "
+                                    + "provided.");
+                }
             } else {
                 if (!hasValidView()) {
                     shouldThrow = true;
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 4708be8..84dfc49 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -38,6 +38,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.flags.Flags.customizableWindowHeaders;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
 
 import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
 
@@ -114,7 +116,6 @@
 import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.BackgroundFallback;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-import com.android.window.flags.Flags;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -1217,14 +1218,15 @@
 
         final boolean hideCaptionBar = fullscreen
                 || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
-        final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption()
-                && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+        final boolean consumingCaptionBar =
+                ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()
+                        && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
                         && hideCaptionBar);
 
         final boolean isOpaqueCaptionBar = customizableWindowHeaders()
                 && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0;
         final boolean consumingOpaqueCaptionBar =
-                Flags.enableCaptionCompatInsetForceConsumptionAlways()
+                ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()
                         && mLastForceConsumingOpaqueCaptionBar
                         && isOpaqueCaptionBar;
 
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index e440dc9..4db9ddf 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -223,17 +223,17 @@
                     "ServiceManager returned a null ProtoLog Configuration Service");
 
             try {
-                var args = new ProtoLogConfigurationService.RegisterClientArgs();
+                var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
 
                 if (viewerConfigFilePath != null) {
                     args.setViewerConfigFile(viewerConfigFilePath);
                 }
 
                 final var groupArgs = Stream.of(groups)
-                        .map(group -> new ProtoLogConfigurationService.RegisterClientArgs
+                        .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(group.name(), group.isLogToLogcat()))
-                        .toArray(
-                                ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new);
+                        .toArray(ProtoLogConfigurationServiceImpl
+                                .RegisterClientArgs.GroupConfig[]::new);
                 args.setGroups(groupArgs);
 
                 mProtoLogConfigurationService.registerClient(this, args);
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index 7031d69..d65aaae 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -16,434 +16,32 @@
 
 package com.android.internal.protolog;
 
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
-
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.tracing.perfetto.DataSourceParams;
-import android.tracing.perfetto.InitArguments;
-import android.tracing.perfetto.Producer;
-import android.util.Log;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-/**
- * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing
- * system. Currently this service has the following roles:
- * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat.
- * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog
- *   clients. This is for two reasons: firstly, because client processes might be frozen so might
- *   not response to the request to dump their viewer config when the trace is stopped; secondly,
- *   multiple processes might be running the same code with the same viewer config, this centralized
- *   service ensures we don't dump the same viewer config multiple times across processes.
- * <p>
- * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to
- * this service on initialization.
- * <p>
- * This service is intended to run on the system server, such that it never gets frozen.
- */
-@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
-public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub {
-    private static final String LOG_TAG = "ProtoLogConfigurationService";
-
-    private final ProtoLogDataSource mDataSource;
-
-    /**
-     * Keeps track of how many of each viewer config file is currently registered.
-     * Use to keep track of which viewer config files are actively being used in tracing and might
-     * need to be dumped on flush.
-     */
-    private final Map<String, Integer> mConfigFileCounts = new HashMap<>();
-    /**
-     * Keeps track of the viewer config file of each client if available.
-     */
-    private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
-
-    /**
-     * Keeps track of all the protolog groups that have been registered by clients and are still
-     * being actively traced.
-     */
-    private final Set<String> mRegisteredGroups = new HashSet<>();
-    /**
-     * Keeps track of all the clients that are actively tracing a given protolog group.
-     */
-    private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();
-
-    /**
-     * Keeps track of whether or not a given group should be logged to logcat.
-     * True when logging to logcat, false otherwise.
-     */
-    private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>();
-
-    /**
-     * Keeps track of all the tracing instance ids that are actively running for ProtoLog.
-     */
-    private final Set<Integer> mRunningInstances = new HashSet<>();
-
-    private final ViewerConfigFileTracer mViewerConfigFileTracer;
-
-    public ProtoLogConfigurationService() {
-        this(ProtoLogDataSource::new, ProtoLogConfigurationService::dumpTransitionTraceConfig);
-    }
-
-    @VisibleForTesting
-    public ProtoLogConfigurationService(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) {
-        this(dataSourceBuilder, ProtoLogConfigurationService::dumpTransitionTraceConfig);
-    }
-
-    @VisibleForTesting
-    public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) {
-        this(ProtoLogDataSource::new, tracer);
-    }
-
-    @VisibleForTesting
-    public ProtoLogConfigurationService(
-            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
-            @NonNull ViewerConfigFileTracer tracer) {
-        mDataSource = dataSourceBuilder.build(
-            this::onTracingInstanceStart,
-            this::onTracingInstanceFlush,
-            this::onTracingInstanceStop
-        );
-
-        // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
-        // receive the lifecycle callbacks of the datasource and write the viewer configs if and
-        // when required to the datasource.
-        Producer.init(InitArguments.DEFAULTS);
-        final var params = new DataSourceParams.Builder()
-                .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
-                .build();
-        mDataSource.register(params);
-
-        mViewerConfigFileTracer = tracer;
-    }
-
-    public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
-        /**
-         * The viewer config file to be registered for this client ProtoLog process.
-         */
-        @Nullable
-        private String mViewerConfigFile = null;
-        /**
-         * The list of all groups that this client protolog process supports and might trace.
-         */
-        @NonNull
-        private String[] mGroups = new String[0];
-        /**
-         * The default logcat status of the ProtoLog client. True is logging to logcat, false
-         * otherwise. The indices should match the indices in {@link mGroups}.
-         */
-        @NonNull
-        private boolean[] mLogcatStatus = new boolean[0];
-
-        public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
-
-        /**
-         * Specify groups to register with this client that will be used for protologging in this
-         * process.
-         * @param groups to register with this client.
-         * @return self
-         */
-        public RegisterClientArgs setGroups(GroupConfig... groups) {
-            mGroups = new String[groups.length];
-            mLogcatStatus = new boolean[groups.length];
-
-            for (int i = 0; i < groups.length; i++) {
-                mGroups[i] = groups[i].group;
-                mLogcatStatus[i] = groups[i].logToLogcat;
-            }
-
-            return this;
-        }
-
-        /**
-         * Set the viewer config file that the logs in this process are using.
-         * @param viewerConfigFile The file path of the viewer config.
-         * @return self
-         */
-        public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
-            mViewerConfigFile = viewerConfigFile;
-
-            return this;
-        }
-
-        @Override
-        @NonNull
-        public String[] getGroups() {
-            return mGroups;
-        }
-
-        @Override
-        @NonNull
-        public boolean[] getGroupsDefaultLogcatStatus() {
-            return mLogcatStatus;
-        }
-
-        @Nullable
-        @Override
-        public String getViewerConfigFile() {
-            return mViewerConfigFile;
-        }
-    }
-
-    @FunctionalInterface
-    public interface ViewerConfigFileTracer {
-        /**
-         * Write the viewer config data to the trace buffer.
-         *
-         * @param dataSource The target datasource to write the viewer config to.
-         * @param viewerConfigFilePath The path of the viewer config file which contains the data we
-         *                             want to write to the trace buffer.
-         * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
-         */
-        void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath);
-    }
-
-    @Override
-    public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
-            throws RemoteException {
-        client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
-
-        final String viewerConfigFile = args.getViewerConfigFile();
-        if (viewerConfigFile != null) {
-            registerViewerConfigFile(client, viewerConfigFile);
-        }
-
-        registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
-    }
-
-    @Override
-    public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
-            @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
-            @NonNull ResultReceiver resultReceiver) throws RemoteException {
-        new ProtoLogCommandHandler(this)
-                .exec(this, in, out, err, args, callback, resultReceiver);
-    }
-
+public interface ProtoLogConfigurationService extends IProtoLogConfigurationService {
     /**
      * Get the list of groups clients have registered to the protolog service.
      * @return The list of ProtoLog groups registered with this service.
      */
     @NonNull
-    public String[] getGroups() {
-        return mRegisteredGroups.toArray(new String[0]);
-    }
-
-    /**
-     * Enable logging target groups to logcat.
-     * @param groups we want to enable logging them to logcat for.
-     */
-    public void enableProtoLogToLogcat(String... groups) {
-        toggleProtoLogToLogcat(true, groups);
-    }
-
-    /**
-     * Disable logging target groups to logcat.
-     * @param groups we want to disable from being logged to logcat.
-     */
-    public void disableProtoLogToLogcat(String... groups) {
-        toggleProtoLogToLogcat(false, groups);
-    }
+    String[] getGroups();
 
     /**
      * Check if a group is logging to logcat
      * @param group The group we want to check for
      * @return True iff we are logging this group to logcat.
      */
-    public boolean isLoggingToLogcat(@NonNull String group) {
-        final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group);
+    boolean isLoggingToLogcat(@NonNull String group);
 
-        if (isLoggingToLogcat == null) {
-            throw new RuntimeException(
-                    "Trying to get logcat logging status of non-registered group " + group);
-        }
+    /**
+     * Enable logging target groups to logcat.
+     * @param groups we want to enable logging them to logcat for.
+     */
+    void enableProtoLogToLogcat(@NonNull String... groups);
 
-        return isLoggingToLogcat;
-    }
-
-    private void registerViewerConfigFile(
-            @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
-        final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
-        mConfigFileCounts.put(viewerConfigFile, count + 1);
-        mClientConfigFiles.put(client, viewerConfigFile);
-    }
-
-    private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
-            @NonNull boolean[] logcatStatuses) throws RemoteException {
-        if (groups.length != logcatStatuses.length) {
-            throw new RuntimeException(
-                    "Expected groups and logcatStatuses to have the same length, "
-                        + "but groups has length " + groups.length
-                        + " and logcatStatuses has length " + logcatStatuses.length);
-        }
-
-        for (int i = 0; i < groups.length; i++) {
-            String group = groups[i];
-            boolean logcatStatus = logcatStatuses[i];
-
-            mRegisteredGroups.add(group);
-
-            mGroupToClients.putIfAbsent(group, new HashSet<>());
-            mGroupToClients.get(group).add(client);
-
-            if (!mLogGroupToLogcatStatus.containsKey(group)) {
-                mLogGroupToLogcatStatus.put(group, logcatStatus);
-            }
-
-            boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group);
-            if (requestedLogToLogcat != logcatStatus) {
-                client.toggleLogcat(requestedLogToLogcat, new String[] { group });
-            }
-        }
-    }
-
-    private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
-        final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
-
-        for (String group : groups) {
-            final var clients = mGroupToClients.get(group);
-
-            if (clients == null) {
-                // No clients associated to this group
-                Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
-                        + " with no registered clients.");
-                continue;
-            }
-
-            for (IProtoLogClient client : clients) {
-                clientToGroups.putIfAbsent(client, new HashSet<>());
-                clientToGroups.get(client).add(group);
-            }
-        }
-
-        for (IProtoLogClient client : clientToGroups.keySet()) {
-            try {
-                client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
-            } catch (RemoteException e) {
-                throw new RuntimeException(
-                        "Failed to toggle logcat status for groups on client", e);
-            }
-        }
-
-        for (String group : groups) {
-            mLogGroupToLogcatStatus.put(group, enabled);
-        }
-    }
-
-    private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
-        mRunningInstances.add(instanceIdx);
-    }
-
-    private void onTracingInstanceFlush() {
-        for (String fileName : mConfigFileCounts.keySet()) {
-            mViewerConfigFileTracer.trace(mDataSource, fileName);
-        }
-    }
-
-    private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
-        mRunningInstances.remove(instanceIdx);
-    }
-
-    private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
-            @NonNull String viewerConfigFilePath) {
-        Utils.dumpViewerConfig(dataSource, () -> {
-            try {
-                return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
-            } catch (FileNotFoundException e) {
-                throw new RuntimeException(
-                        "Failed to load viewer config file " + viewerConfigFilePath, e);
-            }
-        });
-    }
-
-    private void onClientBinderDeath(@NonNull IProtoLogClient client) {
-        // Dump the tracing config now if no other client is going to dump the same config file.
-        String configFile = mClientConfigFiles.get(client);
-        if (configFile != null) {
-            final var newCount = mConfigFileCounts.get(configFile) - 1;
-            mConfigFileCounts.put(configFile, newCount);
-            boolean lastProcessWithViewerConfig = newCount == 0;
-            if (lastProcessWithViewerConfig) {
-                mViewerConfigFileTracer.trace(mDataSource, configFile);
-            }
-        }
-    }
-
-    private static void writeViewerConfigGroup(
-            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
-        final long inGroupToken = pis.start(GROUPS);
-        final long outGroupToken = os.start(GROUPS);
-
-        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            switch (pis.getFieldNumber()) {
-                case (int) ID -> {
-                    int id = pis.readInt(ID);
-                    os.write(ID, id);
-                }
-                case (int) NAME -> {
-                    String name = pis.readString(NAME);
-                    os.write(NAME, name);
-                }
-                case (int) TAG -> {
-                    String tag = pis.readString(TAG);
-                    os.write(TAG, tag);
-                }
-                default ->
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
-            }
-        }
-
-        pis.end(inGroupToken);
-        os.end(outGroupToken);
-    }
-
-    private static void writeViewerConfigMessage(
-            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
-        final long inMessageToken = pis.start(MESSAGES);
-        final long outMessagesToken = os.start(MESSAGES);
-
-        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            switch (pis.getFieldNumber()) {
-                case (int) MESSAGE_ID -> os.write(MESSAGE_ID,
-                        pis.readLong(MESSAGE_ID));
-                case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE));
-                case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL));
-                case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID));
-                case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION));
-                default ->
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
-            }
-        }
-
-        pis.end(inMessageToken);
-        os.end(outMessagesToken);
-    }
+    /**
+     * Disable logging target groups to logcat.
+     * @param groups we want to disable from being logged to logcat.
+     */
+    void disableProtoLogToLogcat(@NonNull String... groups);
 }
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
new file mode 100644
index 0000000..e382ac1
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
@@ -0,0 +1,454 @@
+/*
+ * 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.internal.protolog;
+
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing
+ * system. Currently this service has the following roles:
+ * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat.
+ * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog
+ *   clients. This is for two reasons: firstly, because client processes might be frozen so might
+ *   not response to the request to dump their viewer config when the trace is stopped; secondly,
+ *   multiple processes might be running the same code with the same viewer config, this centralized
+ *   service ensures we don't dump the same viewer config multiple times across processes.
+ * <p>
+ * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to
+ * this service on initialization.
+ * <p>
+ * This service is intended to run on the system server, such that it never gets frozen.
+ */
+@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
+public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub
+        implements ProtoLogConfigurationService {
+    private static final String LOG_TAG = "ProtoLogConfigurationService";
+
+    private final ProtoLogDataSource mDataSource;
+
+    /**
+     * Keeps track of how many of each viewer config file is currently registered.
+     * Use to keep track of which viewer config files are actively being used in tracing and might
+     * need to be dumped on flush.
+     */
+    private final Map<String, Integer> mConfigFileCounts = new HashMap<>();
+    /**
+     * Keeps track of the viewer config file of each client if available.
+     */
+    private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
+
+    /**
+     * Keeps track of all the protolog groups that have been registered by clients and are still
+     * being actively traced.
+     */
+    private final Set<String> mRegisteredGroups = new HashSet<>();
+    /**
+     * Keeps track of all the clients that are actively tracing a given protolog group.
+     */
+    private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();
+
+    /**
+     * Keeps track of whether or not a given group should be logged to logcat.
+     * True when logging to logcat, false otherwise.
+     */
+    private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>();
+
+    /**
+     * Keeps track of all the tracing instance ids that are actively running for ProtoLog.
+     */
+    private final Set<Integer> mRunningInstances = new HashSet<>();
+
+    private final ViewerConfigFileTracer mViewerConfigFileTracer;
+
+    public ProtoLogConfigurationServiceImpl() {
+        this(ProtoLogDataSource::new, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig);
+    }
+
+    @VisibleForTesting
+    public ProtoLogConfigurationServiceImpl(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) {
+        this(dataSourceBuilder, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig);
+    }
+
+    @VisibleForTesting
+    public ProtoLogConfigurationServiceImpl(@NonNull ViewerConfigFileTracer tracer) {
+        this(ProtoLogDataSource::new, tracer);
+    }
+
+    @VisibleForTesting
+    public ProtoLogConfigurationServiceImpl(
+            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
+            @NonNull ViewerConfigFileTracer tracer) {
+        mDataSource = dataSourceBuilder.build(
+            this::onTracingInstanceStart,
+            this::onTracingInstanceFlush,
+            this::onTracingInstanceStop
+        );
+
+        // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
+        // receive the lifecycle callbacks of the datasource and write the viewer configs if and
+        // when required to the datasource.
+        Producer.init(InitArguments.DEFAULTS);
+        final var params = new DataSourceParams.Builder()
+                .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
+                .build();
+        mDataSource.register(params);
+
+        mViewerConfigFileTracer = tracer;
+    }
+
+    public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
+        /**
+         * The viewer config file to be registered for this client ProtoLog process.
+         */
+        @Nullable
+        private String mViewerConfigFile = null;
+        /**
+         * The list of all groups that this client protolog process supports and might trace.
+         */
+        @NonNull
+        private String[] mGroups = new String[0];
+        /**
+         * The default logcat status of the ProtoLog client. True is logging to logcat, false
+         * otherwise. The indices should match the indices in {@link mGroups}.
+         */
+        @NonNull
+        private boolean[] mLogcatStatus = new boolean[0];
+
+        public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
+
+        /**
+         * Specify groups to register with this client that will be used for protologging in this
+         * process.
+         * @param groups to register with this client.
+         * @return self
+         */
+        public RegisterClientArgs setGroups(GroupConfig... groups) {
+            mGroups = new String[groups.length];
+            mLogcatStatus = new boolean[groups.length];
+
+            for (int i = 0; i < groups.length; i++) {
+                mGroups[i] = groups[i].group;
+                mLogcatStatus[i] = groups[i].logToLogcat;
+            }
+
+            return this;
+        }
+
+        /**
+         * Set the viewer config file that the logs in this process are using.
+         * @param viewerConfigFile The file path of the viewer config.
+         * @return self
+         */
+        public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
+            mViewerConfigFile = viewerConfigFile;
+
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public String[] getGroups() {
+            return mGroups;
+        }
+
+        @Override
+        @NonNull
+        public boolean[] getGroupsDefaultLogcatStatus() {
+            return mLogcatStatus;
+        }
+
+        @Nullable
+        @Override
+        public String getViewerConfigFile() {
+            return mViewerConfigFile;
+        }
+    }
+
+    @FunctionalInterface
+    public interface ViewerConfigFileTracer {
+        /**
+         * Write the viewer config data to the trace buffer.
+         *
+         * @param dataSource The target datasource to write the viewer config to.
+         * @param viewerConfigFilePath The path of the viewer config file which contains the data we
+         *                             want to write to the trace buffer.
+         * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
+         */
+        void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath);
+    }
+
+    @Override
+    public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
+            throws RemoteException {
+        client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
+
+        final String viewerConfigFile = args.getViewerConfigFile();
+        if (viewerConfigFile != null) {
+            registerViewerConfigFile(client, viewerConfigFile);
+        }
+
+        registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
+    }
+
+    @Override
+    public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver) throws RemoteException {
+        new ProtoLogCommandHandler(this)
+                .exec(this, in, out, err, args, callback, resultReceiver);
+    }
+
+    /**
+     * Get the list of groups clients have registered to the protolog service.
+     * @return The list of ProtoLog groups registered with this service.
+     */
+    @Override
+    @NonNull
+    public String[] getGroups() {
+        return mRegisteredGroups.toArray(new String[0]);
+    }
+
+    /**
+     * Enable logging target groups to logcat.
+     * @param groups we want to enable logging them to logcat for.
+     */
+    @Override
+    public void enableProtoLogToLogcat(@NonNull String... groups) {
+        toggleProtoLogToLogcat(true, groups);
+    }
+
+    /**
+     * Disable logging target groups to logcat.
+     * @param groups we want to disable from being logged to logcat.
+     */
+    @Override
+    public void disableProtoLogToLogcat(@NonNull String... groups) {
+        toggleProtoLogToLogcat(false, groups);
+    }
+
+    /**
+     * Check if a group is logging to logcat
+     * @param group The group we want to check for
+     * @return True iff we are logging this group to logcat.
+     */
+    @Override
+    public boolean isLoggingToLogcat(@NonNull String group) {
+        final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group);
+
+        if (isLoggingToLogcat == null) {
+            throw new RuntimeException(
+                    "Trying to get logcat logging status of non-registered group " + group);
+        }
+
+        return isLoggingToLogcat;
+    }
+
+    private void registerViewerConfigFile(
+            @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
+        final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
+        mConfigFileCounts.put(viewerConfigFile, count + 1);
+        mClientConfigFiles.put(client, viewerConfigFile);
+    }
+
+    private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
+            @NonNull boolean[] logcatStatuses) throws RemoteException {
+        if (groups.length != logcatStatuses.length) {
+            throw new RuntimeException(
+                    "Expected groups and logcatStatuses to have the same length, "
+                        + "but groups has length " + groups.length
+                        + " and logcatStatuses has length " + logcatStatuses.length);
+        }
+
+        for (int i = 0; i < groups.length; i++) {
+            String group = groups[i];
+            boolean logcatStatus = logcatStatuses[i];
+
+            mRegisteredGroups.add(group);
+
+            mGroupToClients.putIfAbsent(group, new HashSet<>());
+            mGroupToClients.get(group).add(client);
+
+            if (!mLogGroupToLogcatStatus.containsKey(group)) {
+                mLogGroupToLogcatStatus.put(group, logcatStatus);
+            }
+
+            boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group);
+            if (requestedLogToLogcat != logcatStatus) {
+                client.toggleLogcat(requestedLogToLogcat, new String[] { group });
+            }
+        }
+    }
+
+    private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
+        final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
+
+        for (String group : groups) {
+            final var clients = mGroupToClients.get(group);
+
+            if (clients == null) {
+                // No clients associated to this group
+                Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
+                        + " with no registered clients.");
+                continue;
+            }
+
+            for (IProtoLogClient client : clients) {
+                clientToGroups.putIfAbsent(client, new HashSet<>());
+                clientToGroups.get(client).add(group);
+            }
+        }
+
+        for (IProtoLogClient client : clientToGroups.keySet()) {
+            try {
+                client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
+            } catch (RemoteException e) {
+                throw new RuntimeException(
+                        "Failed to toggle logcat status for groups on client", e);
+            }
+        }
+
+        for (String group : groups) {
+            mLogGroupToLogcatStatus.put(group, enabled);
+        }
+    }
+
+    private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+        mRunningInstances.add(instanceIdx);
+    }
+
+    private void onTracingInstanceFlush() {
+        for (String fileName : mConfigFileCounts.keySet()) {
+            mViewerConfigFileTracer.trace(mDataSource, fileName);
+        }
+    }
+
+    private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+        mRunningInstances.remove(instanceIdx);
+    }
+
+    private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
+            @NonNull String viewerConfigFilePath) {
+        Utils.dumpViewerConfig(dataSource, () -> {
+            try {
+                return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException(
+                        "Failed to load viewer config file " + viewerConfigFilePath, e);
+            }
+        });
+    }
+
+    private void onClientBinderDeath(@NonNull IProtoLogClient client) {
+        // Dump the tracing config now if no other client is going to dump the same config file.
+        String configFile = mClientConfigFiles.get(client);
+        if (configFile != null) {
+            final var newCount = mConfigFileCounts.get(configFile) - 1;
+            mConfigFileCounts.put(configFile, newCount);
+            boolean lastProcessWithViewerConfig = newCount == 0;
+            if (lastProcessWithViewerConfig) {
+                mViewerConfigFileTracer.trace(mDataSource, configFile);
+            }
+        }
+    }
+
+    private static void writeViewerConfigGroup(
+            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+        final long inGroupToken = pis.start(GROUPS);
+        final long outGroupToken = os.start(GROUPS);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) ID -> {
+                    int id = pis.readInt(ID);
+                    os.write(ID, id);
+                }
+                case (int) NAME -> {
+                    String name = pis.readString(NAME);
+                    os.write(NAME, name);
+                }
+                case (int) TAG -> {
+                    String tag = pis.readString(TAG);
+                    os.write(TAG, tag);
+                }
+                default ->
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inGroupToken);
+        os.end(outGroupToken);
+    }
+
+    private static void writeViewerConfigMessage(
+            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+        final long inMessageToken = pis.start(MESSAGES);
+        final long outMessagesToken = os.start(MESSAGES);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) MESSAGE_ID -> os.write(MESSAGE_ID,
+                        pis.readLong(MESSAGE_ID));
+                case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE));
+                case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL));
+                case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID));
+                case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION));
+                default ->
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inMessageToken);
+        os.end(outMessagesToken);
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 90cb10a..9a4ff8f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -290,7 +290,6 @@
                 "libasync_safe",
                 "libbinderthreadstateutils",
                 "libdmabufinfo",
-                "libgif",
                 "libgui_window_info_static",
                 "libkernelconfigs",
                 "libnativehelper_lazy",
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 70505a4..b9c3bf7 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -16,16 +16,16 @@
 
 #define LOG_TAG "BLASTBufferQueue"
 
-#include <nativehelper/JNIHelp.h>
-
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
-#include <utils/Log.h>
-#include <utils/RefBase.h>
-
+#include <android_util_Binder.h>
 #include <gui/BLASTBufferQueue.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
 #include "core_jni_helpers.h"
 
 namespace android {
@@ -209,6 +209,12 @@
                           reinterpret_cast<jlong>(transaction));
 }
 
+static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject applyTokenObject) {
+    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+    sp<IBinder> token(ibinderForJavaObject(env, applyTokenObject));
+    return queue->setApplyToken(std::move(token));
+}
+
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         // clang-format off
@@ -227,6 +233,7 @@
         {"nativeSetTransactionHangCallback",
          "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
          (void*)nativeSetTransactionHangCallback},
+        {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken},
         // clang-format on
 };
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f2c70b5..8003bb7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1747,9 +1747,9 @@
     {"linkToDeathNative",   "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
     {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
     {"addFrozenStateChangeCallbackNative",
-        "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
+        "(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
     {"removeFrozenStateChangeCallbackNative",
-        "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
+        "(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
     {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
     {"getExtension",        "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension},
 };
@@ -1774,7 +1774,7 @@
                                    "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
     gBinderProxyOffsets.mInvokeFrozenStateChangeCallback =
             GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback",
-                                   "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/"
+                                   "(Landroid/os/IBinder$FrozenStateChangeCallback;Landroid/os/"
                                    "IBinder;I)V");
     gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
 
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
index 3b29e30..21b5b13 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -69,9 +69,47 @@
     return static_cast<T>(res);
 }
 
+//  Inline variable that specifies the method binding format.
+//  The expected format is 'XX${method}XX', where ${method} represents the original method name.
+//  This variable is shared across all translation units. This is treated as a global variable as
+//  per C++ 17.
+inline std::string jniMethodFormat;
+
+inline static void setJniMethodFormat(std::string value) {
+    jniMethodFormat = value;
+}
+
+// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set.
+// Has no effect otherwise
+inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods,
+                                                    int numMethods) {
+    if (jniMethodFormat.empty()) {
+        return gMethods;
+    }
+    // Make a copy of gMethods with reformatted method names.
+    JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
+    LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
+
+    size_t methodNamePos = jniMethodFormat.find("${method}");
+    LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos,
+                        "Invalid jniMethodFormat: could not find '${method}' in pattern");
+
+    for (int i = 0; i < numMethods; i++) {
+        modifiedMethods[i] = gMethods[i];
+        std::string modifiedName = jniMethodFormat;
+        modifiedName.replace(methodNamePos, 9, gMethods[i].name);
+        char* modifiedNameChars = new char[modifiedName.length() + 1];
+        LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name");
+        std::strcpy(modifiedNameChars, modifiedName.c_str());
+        modifiedMethods[i].name = modifiedNameChars;
+    }
+    return modifiedMethods;
+}
+
 static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
                                        const JNINativeMethod* gMethods, int numMethods) {
-    int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+    const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods);
+    int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
     LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
     return res;
 }
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index 47c97b0..f477d32 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -299,6 +299,14 @@
             NightModeReflectionAction night_mode_reflection_action = 5;
             ReflectionAction reflection_action = 6;
             RemoveFromParentAction remove_from_parent_action = 7;
+            ResourceReflectionAction resource_reflection_action = 8;
+            SetCompoundButtonCheckedAction set_compound_button_checked_action = 9;
+            SetDrawableTintAction set_drawable_tint_action = 10;
+            SetEmptyViewAction set_empty_view_action = 11;
+            SetIntTagAction set_int_tag_action = 12;
+            SetRadioGroupCheckedAction set_radio_group_checked_action = 13;
+            SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14;
+            SetRippleDrawableColorAction set_ripple_drawable_color_action = 15;
         }
     }
 
@@ -374,6 +382,52 @@
     message RemoveFromParentAction {
         optional string view_id = 1;
     }
+
+    message ResourceReflectionAction {
+        optional string view_id = 1;
+        optional string method_name = 2;
+        optional int32 resource_type = 3;
+        optional string res_id = 4;
+        optional int32 parameter_type = 5;
+    }
+
+    message SetCompoundButtonCheckedAction {
+        optional string view_id = 1;
+        optional bool checked = 2;
+    }
+
+    message SetDrawableTintAction {
+        optional string view_id = 1;
+        optional bool target_background = 2;
+        optional int32 color_filter = 3;
+        optional int32 filter_mode = 4;
+    }
+
+    message SetEmptyViewAction {
+        optional string view_id = 1;
+        optional string empty_view_id = 2;
+    }
+
+    message SetIntTagAction {
+        optional string view_id = 1;
+        optional string key = 2;
+        optional int32 tag = 3;
+    }
+
+    message SetRadioGroupCheckedAction {
+        optional string view_id = 1;
+        optional string checked_id = 2;
+    }
+
+    message SetRemoteCollectionItemListAdapterAction {
+        optional string view_id = 1;
+        optional RemoteCollectionItems items = 2;
+    }
+
+    message SetRippleDrawableColorAction {
+        optional string view_id = 1;
+        optional android.content.res.ColorStateListProto color_state_list = 2;
+    }
 }
 
 
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index e60e0b0..db1c779 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -122,7 +122,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
-                android:maxLines="2"
+                android:layout_toStartOf="@id/button_open"
+                android:layout_marginEnd="8dp"
                 android:background="@drawable/resolver_outlined_button_bg"
                 style="?android:attr/borderlessButtonStyle"
                 android:paddingHorizontal="16dp"
@@ -136,7 +137,6 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentEnd="true"
-                android:maxLines="2"
                 android:paddingHorizontal="16dp"
                 android:background="@drawable/resolver_button_bg"
                 style="?android:attr/borderlessButtonStyle"
diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
index 77e8a40..fe54aa8 100644
--- a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
+++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
@@ -30,31 +30,30 @@
 
 public class BfsccTestAppCmdService extends Service {
     private IBfsccTestAppCmdService.Stub mBinder = new IBfsccTestAppCmdService.Stub() {
-        private final LinkedBlockingQueue<IBinder.IFrozenStateChangeCallback.State> mNotifications =
+        private final LinkedBlockingQueue<Integer> mNotifications =
                 new LinkedBlockingQueue<>();
 
         @Override
         public void listenTo(IBinder binder) throws RemoteException {
             binder.addFrozenStateChangeCallback(
-                    (IBinder who, IBinder.IFrozenStateChangeCallback.State state)
-                            -> mNotifications.offer(state));
+                    (IBinder who, int state) -> mNotifications.offer(state));
         }
 
         @Override
         public boolean[] waitAndConsumeNotifications() {
             List<Boolean> results = new ArrayList<>();
             try {
-                IBinder.IFrozenStateChangeCallback.State state =
-                        mNotifications.poll(5, TimeUnit.SECONDS);
+                Integer state = mNotifications.poll(5, TimeUnit.SECONDS);
                 if (state != null) {
-                    results.add(state == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+                    results.add(
+                            state.intValue() == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
                 }
             } catch (InterruptedException e) {
                 return null;
             }
             while (mNotifications.size() > 0) {
-                results.add(mNotifications.poll()
-                        == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+                results.add(mNotifications.poll().intValue()
+                        == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
             }
             boolean[] convertedResults = new boolean[results.size()];
             for (int i = 0; i < results.size(); i++) {
diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
index ee2e7e0..195a18a 100644
--- a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
+++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
@@ -52,7 +52,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
- * Tests functionality of {@link android.os.IBinder.IFrozenStateChangeCallback}.
+ * Tests functionality of {@link android.os.IBinder.FrozenStateChangeCallback}.
  */
 @RunWith(AndroidJUnit4.class)
 @IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
@@ -157,7 +157,7 @@
     @Test
     public void onStateChangeNotCalledAfterCallbackRemoved() throws Exception {
         final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>();
-        IBinder.IFrozenStateChangeCallback callback;
+        IBinder.FrozenStateChangeCallback callback;
         if ((callback = createCallback(mBfsccTestAppCmdService.asBinder(), results)) == null) {
             return;
         }
@@ -171,7 +171,7 @@
     public void multipleCallbacks() throws Exception {
         final LinkedBlockingQueue<Boolean> results1 = new LinkedBlockingQueue<>();
         final LinkedBlockingQueue<Boolean> results2 = new LinkedBlockingQueue<>();
-        IBinder.IFrozenStateChangeCallback callback1;
+        IBinder.FrozenStateChangeCallback callback1;
         if ((callback1 = createCallback(mBfsccTestAppCmdService.asBinder(), results1)) == null) {
             return;
         }
@@ -197,8 +197,8 @@
     public void onStateChangeCalledWithTheRightBinder() throws Exception {
         final IBinder binder = mBfsccTestAppCmdService.asBinder();
         final LinkedBlockingQueue<IBinder> results = new LinkedBlockingQueue<>();
-        IBinder.IFrozenStateChangeCallback callback =
-                (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> results.offer(who);
+        IBinder.FrozenStateChangeCallback callback =
+                (IBinder who, int state) -> results.offer(who);
         try {
             binder.addFrozenStateChangeCallback(callback);
         } catch (UnsupportedOperationException e) {
@@ -221,12 +221,12 @@
         }
     }
 
-    private IBinder.IFrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue)
+    private IBinder.FrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue)
             throws RemoteException {
         try {
-            final IBinder.IFrozenStateChangeCallback callback =
-                    (IBinder who, IBinder.IFrozenStateChangeCallback.State state) ->
-                            queue.offer(state == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+            final IBinder.FrozenStateChangeCallback callback =
+                    (IBinder who, int state) ->
+                            queue.offer(state == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
             binder.addFrozenStateChangeCallback(callback);
             return callback;
         } catch (UnsupportedOperationException e) {
diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
index 32345e6..dd40695 100644
--- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
@@ -16,7 +16,7 @@
 
 package android.window.flags;
 
-import static android.window.flags.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE;
 
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
 import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS;
@@ -75,7 +75,7 @@
     public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_OFF_SETTING);
         // In absence of dev options, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
     }
 
 
@@ -84,7 +84,7 @@
     public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_ON_SETTING);
 
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
     }
 
     @Test
@@ -93,7 +93,7 @@
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For overridableFlag, for unset overrides, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
     }
 
     @Test
@@ -103,7 +103,7 @@
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For overridableFlag, for unset overrides, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
     }
 
     @Test
@@ -112,7 +112,7 @@
         setOverride(null);
 
         // For overridableFlag, in absence of overrides, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
     }
 
     @Test
@@ -122,7 +122,7 @@
         setOverride(null);
 
         // For overridableFlag, in absence of overrides, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
     }
 
     @Test
@@ -131,7 +131,7 @@
         setOverride(-2);
 
         // For overridableFlag, for unrecognized overrides, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
     }
 
     @Test
@@ -141,7 +141,7 @@
         setOverride(-2);
 
         // For overridableFlag, for unrecognizable overrides, follow flag
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
     }
 
     @Test
@@ -150,7 +150,7 @@
         setOverride(OVERRIDE_OFF_SETTING);
 
         // For overridableFlag, follow override if they exist
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
     }
 
     @Test
@@ -160,7 +160,7 @@
         setOverride(OVERRIDE_ON_SETTING);
 
         // For overridableFlag, follow override if they exist
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
     }
 
     @Test
@@ -169,12 +169,12 @@
         setOverride(OVERRIDE_OFF_SETTING);
 
         // For overridableFlag, follow override if they exist
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
 
         setOverride(OVERRIDE_ON_SETTING);
 
         // Keep overrides constant through the process
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
     }
 
     @Test
@@ -184,12 +184,12 @@
         setOverride(OVERRIDE_ON_SETTING);
 
         // For overridableFlag, follow override if they exist
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
 
         setOverride(OVERRIDE_OFF_SETTING);
 
         // Keep overrides constant through the process
-        assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+        assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
     }
 
     @Test
@@ -199,7 +199,7 @@
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
     }
 
     @Test
@@ -208,7 +208,7 @@
     public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_UNSET_SETTING);
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
     }
 
     @Test
@@ -221,7 +221,7 @@
         setOverride(OVERRIDE_ON_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
     }
 
     @Test
@@ -231,7 +231,7 @@
         setOverride(OVERRIDE_ON_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
     }
 
     @Test
@@ -244,7 +244,7 @@
         setOverride(OVERRIDE_OFF_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
     }
 
     @Test
@@ -254,7 +254,7 @@
         setOverride(OVERRIDE_OFF_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
     }
 
     @Test
@@ -267,7 +267,7 @@
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
     }
 
     @Test
@@ -280,7 +280,7 @@
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
     }
 
     @Test
@@ -293,7 +293,7 @@
         setOverride(OVERRIDE_ON_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
     }
 
     @Test
@@ -306,7 +306,7 @@
         setOverride(OVERRIDE_ON_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
     }
 
     @Test
@@ -319,7 +319,7 @@
         setOverride(OVERRIDE_OFF_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
     }
 
     @Test
@@ -332,7 +332,7 @@
         setOverride(OVERRIDE_OFF_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
     }
 
     private void setOverride(Integer setting) {
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 5af272c..d6b2a78 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -117,6 +117,7 @@
 
         // Simulate a trace session and see if begin / end are invoked.
         assertThat(monitor.begin(mSurfaceControl, mActivity.getApplicationContext(),
+                mActivity.getMainThreadHandler(),
                 Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).begin();
         assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 397cdcf..67de25e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -125,12 +125,12 @@
         }
 
         @Override
-        public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback)
+        public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback)
                 throws RemoteException {
         }
 
         @Override
-        public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) {
+        public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) {
             return false;
         }
 
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index c52f700..90723b2 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import android.annotation.NonNull;
+import android.os.IBinder;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -47,6 +48,7 @@
             long frameNumber);
     private static native void nativeSetTransactionHangCallback(long ptr,
             TransactionHangCallback callback);
+    private static native void nativeSetApplyToken(long ptr, IBinder applyToken);
 
     public interface TransactionHangCallback {
         void onTransactionHang(String reason);
@@ -204,4 +206,8 @@
     public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
         nativeSetTransactionHangCallback(mNativeObject, hangCallback);
     }
+
+    public void setApplyToken(IBinder applyToken) {
+        nativeSetApplyToken(mNativeObject, applyToken);
+    }
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 9027bf3..88878c6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -40,6 +40,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
+import android.app.TaskInfo;
 import android.app.WindowConfiguration;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -339,6 +340,52 @@
         return target;
     }
 
+    /**
+     * Creates a new RemoteAnimationTarget from the provided change and leash
+     */
+    public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order,
+            boolean isTranslucent) {
+        int taskId;
+        boolean isNotInRecents;
+        WindowConfiguration windowConfiguration;
+
+        if (taskInfo != null) {
+            taskId = taskInfo.taskId;
+            isNotInRecents = !taskInfo.isRunning;
+            windowConfiguration = taskInfo.configuration.windowConfiguration;
+        } else {
+            taskId = INVALID_TASK_ID;
+            isNotInRecents = true;
+            windowConfiguration = new WindowConfiguration();
+        }
+
+        Rect localBounds = new Rect();
+        RemoteAnimationTarget target = new RemoteAnimationTarget(
+                taskId,
+                newModeToLegacyMode(mode),
+                // TODO: once we can properly sync transactions across process,
+                // then get rid of this leash.
+                leash,
+                isTranslucent,
+                null,
+                // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+                new Rect(0, 0, 0, 0),
+                order,
+                null,
+                localBounds,
+                new Rect(),
+                windowConfiguration,
+                isNotInRecents,
+                null,
+                new Rect(),
+                taskInfo,
+                false,
+                INVALID_WINDOW_TYPE
+        );
+        return target;
+    }
+
     private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
             SurfaceControl leash) {
         return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 452d12a..7e6f434 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -46,7 +46,6 @@
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizerController;
-import android.window.ScreenCapture;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
@@ -55,7 +54,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.api.CompatUIHandler;
@@ -74,7 +72,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.function.Consumer;
 
 /**
  * Unified task organizer for all components in the shell.
@@ -561,19 +558,6 @@
         mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
     }
 
-    /**
-     * Take a screenshot of a task.
-     */
-    public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
-            Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
-        final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
-        if (info == null) {
-            return;
-        }
-        ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
-    }
-
-
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f478b44..3e5adf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -117,6 +117,7 @@
      */
     private static final long MAX_ANIMATION_DURATION = 2000;
     private final LatencyTracker mLatencyTracker;
+    @ShellMainThread private final Handler mHandler;
 
     /** True when a back gesture is ongoing */
     private boolean mBackGestureStarted = false;
@@ -218,7 +219,8 @@
             @NonNull BackAnimationBackground backAnimationBackground,
             ShellBackAnimationRegistry shellBackAnimationRegistry,
             ShellCommandHandler shellCommandHandler,
-            Transitions transitions) {
+            Transitions transitions,
+            @ShellMainThread Handler handler) {
         this(
                 shellInit,
                 shellController,
@@ -230,7 +232,8 @@
                 backAnimationBackground,
                 shellBackAnimationRegistry,
                 shellCommandHandler,
-                transitions);
+                transitions,
+                handler);
     }
 
     @VisibleForTesting
@@ -245,7 +248,8 @@
             @NonNull BackAnimationBackground backAnimationBackground,
             ShellBackAnimationRegistry shellBackAnimationRegistry,
             ShellCommandHandler shellCommandHandler,
-            Transitions transitions) {
+            Transitions transitions,
+            @NonNull @ShellMainThread Handler handler) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -263,6 +267,7 @@
         mTransitions = transitions;
         mBackTransitionHandler = new BackTransitionHandler();
         mTransitions.addHandler(mBackTransitionHandler);
+        mHandler = handler;
         updateTouchableArea();
     }
 
@@ -399,7 +404,7 @@
         }
     }
 
-    private static class IBackAnimationImpl extends IBackAnimation.Stub
+    private class IBackAnimationImpl extends IBackAnimation.Stub
             implements ExternalInterfaceBinder {
         private BackAnimationController mController;
 
@@ -417,7 +422,8 @@
                                     callback,
                                     runner,
                                     controller.mContext,
-                                    CUJ_PREDICTIVE_BACK_HOME)));
+                                    CUJ_PREDICTIVE_BACK_HOME,
+                                    mHandler)));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index e24df0b..9ca9b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -31,6 +32,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 /**
  * Used to register the animation callback and runner, it will trigger result if gesture was finish
@@ -45,6 +47,8 @@
     private final IRemoteAnimationRunner mRunner;
     private final @CujType int mCujType;
     private final Context mContext;
+    @ShellMainThread
+    private final Handler mHandler;
 
     // Whether we are waiting to receive onAnimationStart
     private boolean mWaitingAnimation;
@@ -56,18 +60,35 @@
             @NonNull IOnBackInvokedCallback callback,
             @NonNull IRemoteAnimationRunner runner,
             @NonNull Context context,
-            @CujType int cujType) {
+            @CujType int cujType,
+            @ShellMainThread Handler handler) {
         mCallback = callback;
         mRunner = runner;
         mCujType = cujType;
         mContext = context;
+        mHandler = handler;
     }
 
     public BackAnimationRunner(
             @NonNull IOnBackInvokedCallback callback,
             @NonNull IRemoteAnimationRunner runner,
-            @NonNull Context context) {
-        this(callback, runner, context, NO_CUJ);
+            @NonNull Context context,
+            @ShellMainThread Handler handler
+    ) {
+        this(callback, runner, context, NO_CUJ, handler);
+    }
+
+    /**
+     * @deprecated Use {@link BackAnimationRunner} constructor providing an handler for the ui
+     * thread of the animation.
+     */
+    @Deprecated
+    public BackAnimationRunner(
+            @NonNull IOnBackInvokedCallback callback,
+            @NonNull IRemoteAnimationRunner runner,
+            @NonNull Context context
+    ) {
+        this(callback, runner, context, NO_CUJ, context.getMainThreadHandler());
     }
 
     /** Returns the registered animation runner */
@@ -100,7 +121,7 @@
         mWaitingAnimation = false;
         if (shouldMonitorCUJ(apps)) {
             interactionJankMonitor.begin(
-                    apps[0].leash, mContext, mCujType);
+                    apps[0].leash, mContext, mHandler, mCujType);
         }
         try {
             getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 32e809a..3733930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -26,6 +26,7 @@
 import android.graphics.PointF
 import android.graphics.Rect
 import android.graphics.RectF
+import android.os.Handler
 import android.os.RemoteException
 import android.util.TimeUtils
 import android.view.Choreographer
@@ -53,6 +54,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -61,7 +63,8 @@
     private val context: Context,
     private val background: BackAnimationBackground,
     private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-    protected val transaction: SurfaceControl.Transaction
+    protected val transaction: SurfaceControl.Transaction,
+    @ShellMainThread handler: Handler,
 ) : ShellBackAnimation() {
 
     protected val startClosingRect = RectF()
@@ -80,7 +83,13 @@
     private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
 
     private val backAnimationRunner =
-        BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY)
+        BackAnimationRunner(
+            Callback(),
+            Runner(),
+            context,
+            Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+            handler,
+        )
     private val initialTouchPos = PointF()
     private val transformMatrix = Matrix()
     private val tmpFloat9 = FloatArray(9)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 3fcceca..7a56979 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -34,6 +34,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.view.Choreographer;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -52,6 +53,7 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import javax.inject.Inject;
 
@@ -113,9 +115,10 @@
     private float mVerticalMargin;
 
     @Inject
-    public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+    public CrossTaskBackAnimation(Context context, BackAnimationBackground background,
+            @ShellMainThread Handler handler) {
         mBackAnimationRunner = new BackAnimationRunner(
-                new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
+                new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK, handler);
         mBackground = background;
         mContext = context;
         loadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index b02f97b..2f7666b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.graphics.Rect
 import android.graphics.RectF
+import android.os.Handler
 import android.util.MathUtils
 import android.view.SurfaceControl
 import android.view.animation.Animation
@@ -30,6 +31,7 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
@@ -40,13 +42,15 @@
     background: BackAnimationBackground,
     rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
     transaction: SurfaceControl.Transaction,
-    private val customAnimationLoader: CustomAnimationLoader
+    private val customAnimationLoader: CustomAnimationLoader,
+    @ShellMainThread handler: Handler,
 ) :
     CrossActivityBackAnimation(
         context,
         background,
         rootTaskDisplayAreaOrganizer,
-        transaction
+        transaction,
+        handler
     ) {
 
     private var enterAnimation: Animation? = null
@@ -59,7 +63,8 @@
     constructor(
         context: Context,
         background: BackAnimationBackground,
-        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+        @ShellMainThread handler: Handler,
     ) : this(
         context,
         background,
@@ -67,7 +72,8 @@
         SurfaceControl.Transaction(),
         CustomAnimationLoader(
             TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
-        )
+        ),
+        handler,
     )
 
     override fun preparePreCommitClosingRectMovement(swipeEdge: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 66d8a5f..eecd769 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -16,11 +16,13 @@
 package com.android.wm.shell.back
 
 import android.content.Context
+import android.os.Handler
 import android.view.SurfaceControl
 import android.window.BackEvent
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
 import javax.inject.Inject
 import kotlin.math.max
 
@@ -30,13 +32,15 @@
 constructor(
     context: Context,
     background: BackAnimationBackground,
-    rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    @ShellMainThread handler: Handler,
 ) :
     CrossActivityBackAnimation(
         context,
         background,
         rootTaskDisplayAreaOrganizer,
-        SurfaceControl.Transaction()
+        SurfaceControl.Transaction(),
+        handler
     ) {
 
     private val postCommitInterpolator = Interpolators.EMPHASIZED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d253736..c545d73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2007,7 +2007,7 @@
         @Override
         public void selectionChanged(BubbleViewProvider selectedBubble) {
             // Only need to update the layer view if we're currently expanded for selection changes.
-            if (mLayerView != null && mLayerView.isExpanded()) {
+            if (mLayerView != null && isStackExpanded()) {
                 mLayerView.showExpandedView(selectedBubble);
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1367b7e..1c9c195 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -186,10 +186,6 @@
         if (expandedView == null) {
             return;
         }
-        if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
-            // Already showing this bubble, skip animating
-            return;
-        }
         if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
             removeView(mExpandedView);
             mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b8aa1b1..4b55fd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -48,6 +48,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.view.Display;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -75,6 +76,7 @@
 import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
@@ -116,6 +118,8 @@
             new PathInterpolator(0.2f, 0f, 0f, 1f);
     private static final Interpolator GROW_INTERPOLATOR =
             new PathInterpolator(0.45f, 0f, 0.5f, 1f);
+    @ShellMainThread
+    private final Handler mHandler;
 
     private int mDividerWindowWidth;
     private int mDividerInsets;
@@ -166,7 +170,8 @@
             SplitLayoutHandler splitLayoutHandler,
             SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
             DisplayController displayController, DisplayImeController displayImeController,
-            ShellTaskOrganizer taskOrganizer, int parallaxType) {
+            ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) {
+        mHandler = handler;
         mContext = context.createConfigurationContext(configuration);
         mOrientation = configuration.orientation;
         mRotation = configuration.windowConfiguration.getRotation();
@@ -598,7 +603,8 @@
     }
 
     void onStartDragging() {
-        mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE);
+        mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler,
+                CUJ_SPLIT_SCREEN_RESIZE);
     }
 
     void onDraggingCancelled() {
@@ -756,7 +762,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mInteractionJankMonitor.begin(getDividerLeash(),
-                        mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+                        mContext, mHandler, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
             }
 
             @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4adea23..722fe1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -444,7 +444,9 @@
             BackAnimationBackground backAnimationBackground,
             Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
             ShellCommandHandler shellCommandHandler,
-            Transitions transitions) {
+            Transitions transitions,
+            @ShellMainThread Handler handler
+    ) {
         if (BackAnimationController.IS_ENABLED) {
             return shellBackAnimationRegistry.map(
                     (animations) ->
@@ -457,7 +459,8 @@
                                     backAnimationBackground,
                                     animations,
                                     shellCommandHandler,
-                                    transitions));
+                                    transitions,
+                                    handler));
         }
         return Optional.empty();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b151c8b..b47adb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -355,7 +355,8 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellAnimationThread ShellExecutor animExecutor,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler) {
         return new FreeformTaskTransitionHandler(
                 shellInit,
                 transitions,
@@ -365,7 +366,8 @@
                 mainExecutor,
                 animExecutor,
                 desktopModeTaskRepository,
-                interactionJankMonitor);
+                interactionJankMonitor,
+                handler);
     }
 
     @WMSingleton
@@ -487,10 +489,11 @@
     @Provides
     static RecentsTransitionHandler provideRecentsTransitionHandler(
             ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
             Transitions transitions,
             Optional<RecentTasksController> recentTasksController,
             HomeTransitionObserver homeTransitionObserver) {
-        return new RecentsTransitionHandler(shellInit, transitions,
+        return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
                 recentTasksController.orElse(null), homeTransitionObserver);
     }
 
@@ -616,6 +619,7 @@
             RecentsTransitionHandler recentsTransitionHandler,
             MultiInstanceHelper multiInstanceHelper,
             @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             Optional<RecentTasksController> recentTasksController,
             InteractionJankMonitor interactionJankMonitor) {
@@ -628,7 +632,7 @@
                 dragToDesktopTransitionHandler, desktopModeTaskRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
-                recentTasksController.orElse(null), interactionJankMonitor);
+                recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
     }
 
     @WMSingleton
@@ -638,7 +642,8 @@
             Transitions transitions,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             ShellTaskOrganizer shellTaskOrganizer,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler) {
         int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
         if (!DesktopModeStatus.canEnterDesktopMode(context)
                 || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context)
@@ -652,7 +657,8 @@
                         shellTaskOrganizer,
                         maxTaskLimit,
                         interactionJankMonitor,
-                        context)
+                        context,
+                        handler)
         );
     }
 
@@ -699,9 +705,10 @@
     static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
             Transitions transitions,
             Context context,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler) {
         return new ExitDesktopTaskTransitionHandler(
-            transitions, context, interactionJankMonitor);
+            transitions, context, interactionJankMonitor, handler);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..3a4764d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -101,7 +101,8 @@
             DisplayInsetsController displayInsetsController,
             TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
-            @ShellMainThread ShellExecutor mainExecutor) {
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler handler) {
         return Optional.ofNullable(PipController.create(
                 context, shellInit, shellCommandHandler, shellController,
                 displayController, pipAnimationController, pipAppOpsListener,
@@ -111,7 +112,7 @@
                 pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
                 displayInsetsController, pipTabletopController, oneHandedController,
-                mainExecutor));
+                mainExecutor, handler));
     }
 
     // Handler is used by Icon.loadDrawableAsync
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 c74f4a7..853284a 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
@@ -35,6 +35,7 @@
 import android.graphics.Rect
 import android.graphics.Region
 import android.os.Binder
+import android.os.Handler
 import android.os.IBinder
 import android.os.SystemProperties
 import android.util.Size
@@ -136,7 +137,8 @@
     @ShellMainThread private val mainExecutor: ShellExecutor,
     private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
     private val recentTasksController: RecentTasksController?,
-    private val interactionJankMonitor: InteractionJankMonitor
+    private val interactionJankMonitor: InteractionJankMonitor,
+    @ShellMainThread private val handler: Handler,
 ) :
     RemoteCallable<DesktopTasksController>,
     Transitions.TransitionHandler,
@@ -387,7 +389,7 @@
         taskSurface: SurfaceControl,
     ) {
         logV("startDragToDesktop taskId=%d", taskInfo.taskId)
-        interactionJankMonitor.begin(taskSurface, context,
+        interactionJankMonitor.begin(taskSurface, context, handler,
             CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         dragToDesktopTransitionHandler.startDragToDesktopTransition(
             taskInfo.taskId,
@@ -791,7 +793,7 @@
         releaseVisualIndicator()
         if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
             interactionJankMonitor.begin(
-                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+                taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
             )
 
             // reposition non-resizable app back to its original position before being dragged
@@ -804,7 +806,7 @@
             )
         } else {
             interactionJankMonitor.begin(
-                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+                taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
             )
             snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
         }
@@ -1604,7 +1606,7 @@
         when (indicatorType) {
             IndicatorType.TO_DESKTOP_INDICATOR -> {
                 // Start a new jank interaction for the drag release to desktop window animation.
-                interactionJankMonitor.begin(taskSurface, context,
+                interactionJankMonitor.begin(taskSurface, context, handler,
                     CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
                 finalizeDragToDesktop(taskInfo)
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 597637d..dae37f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.content.Context
+import android.os.Handler
 import android.os.IBinder
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_TO_BACK
@@ -29,6 +30,7 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionObserver
 
@@ -45,7 +47,8 @@
         private val shellTaskOrganizer: ShellTaskOrganizer,
         private val maxTasksLimit: Int,
         private val interactionJankMonitor: InteractionJankMonitor,
-        private val context: Context
+        private val context: Context,
+        @ShellMainThread private val handler: Handler,
 ) {
     private val minimizeTransitionObserver = MinimizeTransitionObserver()
     @VisibleForTesting
@@ -125,7 +128,7 @@
             if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
                 // Begin minimize window CUJ instrumentation.
                 interactionJankMonitor.begin(
-                    mActiveTaskDetails.transitionInfo?.rootLeash, context,
+                    mActiveTaskDetails.transitionInfo?.rootLeash, context, handler,
                     CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
                 )
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index e87be52..dedd44f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.IBinder;
 import android.util.DisplayMetrics;
 import android.view.SurfaceControl;
@@ -44,6 +45,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.Cuj;
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 
@@ -63,6 +65,8 @@
     private final Context mContext;
     private final Transitions mTransitions;
     private final InteractionJankMonitor mInteractionJankMonitor;
+    @ShellMainThread
+    private final Handler mHandler;
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
     private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -71,20 +75,24 @@
     public ExitDesktopTaskTransitionHandler(
             Transitions transitions,
             Context context,
-            InteractionJankMonitor interactionJankMonitor
-            ) {
-        this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
+            InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler
+    ) {
+        this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor,
+                handler);
     }
 
     private ExitDesktopTaskTransitionHandler(
             Transitions transitions,
             Supplier<SurfaceControl.Transaction> supplier,
             Context context,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler) {
         mTransitions = transitions;
         mTransactionSupplier = supplier;
         mContext = context;
         mInteractionJankMonitor = interactionJankMonitor;
+        mHandler = handler;
     }
 
     /**
@@ -154,7 +162,7 @@
             final SurfaceControl sc = change.getLeash();
             final Rect endBounds = change.getEndAbsBounds();
             mInteractionJankMonitor
-                .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
+                    .begin(sc, mContext, mHandler, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
             // Hide the first (fullscreen) frame because the animation will start from the freeform
             // size.
             startT.hide(sc)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 832e2d2..517e209 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -28,6 +28,7 @@
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.view.SurfaceControl;
@@ -43,6 +44,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -65,6 +67,8 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
+    @ShellMainThread
+    private final Handler mHandler;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
@@ -79,7 +83,8 @@
             ShellExecutor mainExecutor,
             ShellExecutor animExecutor,
             DesktopModeTaskRepository desktopModeTaskRepository,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler) {
         mTransitions = transitions;
         mContext = context;
         mWindowDecorViewModel = windowDecorViewModel;
@@ -88,6 +93,7 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
+        mHandler = handler;
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -267,7 +273,7 @@
                         change.getTaskInfo().displayId) == 1) {
             // Starting the jank trace if closing the last window in desktop mode.
             mInteractionJankMonitor.begin(
-                    sc, mContext, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
+                    sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
         }
         animator.addListener(
                 new AnimatorListenerAdapter() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 1827923..15472eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -203,7 +204,7 @@
             DisplayController displayController, DisplayLayout displayLayout,
             TaskStackListenerImpl taskStackListener,
             InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
-            ShellExecutor mainExecutor, Handler mainHandler) {
+            ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
@@ -217,7 +218,7 @@
                 mainExecutor);
         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
                 context, displayLayout, settingsUtil, animationController, tutorialHandler,
-                jankMonitor, mainExecutor);
+                jankMonitor, mainExecutor, mainHandler);
         OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
         return new OneHandedController(context, shellInit, shellCommandHandler, shellController,
                 displayController, organizer, touchHandler, tutorialHandler, settingsUtil,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index d157ca8..95e633d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -42,6 +43,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -70,6 +72,8 @@
     private final OneHandedSettingsUtil mOneHandedSettingsUtil;
     private final InteractionJankMonitor mJankMonitor;
     private final Context mContext;
+    @ShellMainThread
+    private final Handler mHandler;
 
     private boolean mIsReady;
     private float mLastVisualOffset = 0;
@@ -136,9 +140,11 @@
             OneHandedAnimationController animationController,
             OneHandedTutorialHandler tutorialHandler,
             InteractionJankMonitor jankMonitor,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor,
+            @ShellMainThread Handler handler) {
         super(mainExecutor);
         mContext = context;
+        mHandler = handler;
         setDisplayLayout(displayLayout);
         mOneHandedSettingsUtil = oneHandedSettingsUtil;
         mAnimationController = animationController;
@@ -333,7 +339,7 @@
                 getDisplayAreaTokenMap().entrySet().iterator().next();
         final InteractionJankMonitor.Configuration.Builder builder =
                 InteractionJankMonitor.Configuration.Builder.withSurface(
-                        cujType, mContext, firstEntry.getValue());
+                        cujType, mContext, firstEntry.getValue(), mHandler);
         if (!TextUtils.isEmpty(tag)) {
             builder.setTag(tag);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index deb7691..af68442 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -44,6 +44,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.Pair;
@@ -93,6 +94,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -146,6 +148,8 @@
     private Optional<OneHandedController> mOneHandedController;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
+    @ShellMainThread
+    private final Handler mHandler;
     protected final PipImpl mImpl;
 
     private final Rect mTmpInsetBounds = new Rect();
@@ -405,7 +409,8 @@
             DisplayInsetsController displayInsetsController,
             TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor,
+            Handler handler) {
         if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Device doesn't support Pip feature", TAG);
@@ -418,7 +423,8 @@
                 pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
                 pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor,
+                handler)
                 .mImpl;
     }
 
@@ -446,11 +452,13 @@
             DisplayInsetsController displayInsetsController,
             TabletopModeController tabletopModeController,
             Optional<OneHandedController> oneHandedController,
-            ShellExecutor mainExecutor
+            ShellExecutor mainExecutor,
+            @ShellMainThread Handler handler
     ) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
+        mHandler = handler;
         mImpl = new PipImpl();
         mWindowManagerShellWrapper = windowManagerShellWrapper;
         mDisplayController = displayController;
@@ -1047,7 +1055,8 @@
         // Begin InteractionJankMonitor with PIP transition CUJs
         final InteractionJankMonitor.Configuration.Builder builder =
                 InteractionJankMonitor.Configuration.Builder.withSurface(
-                        CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl())
+                                CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl(),
+                                mHandler)
                 .setTag(getTransitionTag(direction))
                 .setTimeout(2000);
         InteractionJankMonitor.getInstance().begin(builder);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index c660000..8077aee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -20,9 +20,12 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_SLEEP;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,6 +44,7 @@
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -64,6 +68,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,10 +84,15 @@
  * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
  * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
  */
-public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+public class RecentsTransitionHandler implements Transitions.TransitionHandler,
+        Transitions.TransitionObserver {
     private static final String TAG = "RecentsTransitionHandler";
 
+    // A placeholder for a synthetic transition that isn't backed by a true system transition
+    public static final IBinder SYNTHETIC_TRANSITION = new Binder();
+
     private final Transitions mTransitions;
+    private final ShellTaskOrganizer mShellTaskOrganizer;
     private final ShellExecutor mExecutor;
     @Nullable
     private final RecentTasksController mRecentTasksController;
@@ -99,19 +109,26 @@
     private final HomeTransitionObserver mHomeTransitionObserver;
     private @Nullable Color mBackgroundColor;
 
-    public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+    public RecentsTransitionHandler(
+            @NonNull ShellInit shellInit,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @NonNull Transitions transitions,
             @Nullable RecentTasksController recentTasksController,
-            HomeTransitionObserver homeTransitionObserver) {
+            @NonNull HomeTransitionObserver homeTransitionObserver) {
+        mShellTaskOrganizer = shellTaskOrganizer;
         mTransitions = transitions;
         mExecutor = transitions.getMainExecutor();
         mRecentTasksController = recentTasksController;
         mHomeTransitionObserver = homeTransitionObserver;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
         if (recentTasksController == null) return;
-        shellInit.addInitCallback(() -> {
-            recentTasksController.setTransitionHandler(this);
-            transitions.addHandler(this);
-        }, this);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mRecentTasksController.setTransitionHandler(this);
+        mTransitions.addHandler(this);
+        mTransitions.registerObserver(this);
     }
 
     /** Register a mixer handler. {@see RecentsMixedHandler}*/
@@ -138,17 +155,59 @@
         mBackgroundColor = color;
     }
 
+    /**
+     * Starts a new real/synthetic recents transition.
+     */
     @VisibleForTesting
     public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
             IApplicationThread appThread, IRecentsAnimationRunner listener) {
+        // only care about latest one.
+        mAnimApp = appThread;
+
+        // TODO(b/366021931): Formalize this later
+        final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
+        if (isSyntheticRequest) {
+            return startSyntheticRecentsTransition(listener);
+        } else {
+            return startRealRecentsTransition(intent, fillIn, options, listener);
+        }
+    }
+
+    /**
+     * Starts a synthetic recents transition that is not backed by a real WM transition.
+     */
+    private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                "RecentsTransitionHandler.startRecentsTransition(synthetic)");
+        final RecentsController lastController = getLastController();
+        if (lastController != null) {
+            lastController.cancel(lastController.isSyntheticTransition()
+                    ? "existing_running_synthetic_transition"
+                    : "existing_running_transition");
+            return null;
+        }
+
+        // Create a new synthetic transition and start it immediately
+        final RecentsController controller = new RecentsController(listener);
+        controller.startSyntheticTransition();
+        mControllers.add(controller);
+        return SYNTHETIC_TRANSITION;
+    }
+
+    /**
+     * Starts a real WM-backed recents transition.
+     */
+    private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+            IRecentsAnimationRunner listener) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                 "RecentsTransitionHandler.startRecentsTransition");
 
-        // only care about latest one.
-        mAnimApp = appThread;
-        WindowContainerTransaction wct = new WindowContainerTransaction();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.sendPendingIntent(intent, fillIn, options);
-        final RecentsController controller = new RecentsController(listener);
+
+        // Find the mixed handler which should handle this request (if we are in a state where a
+        // mixed handler is needed).  This is slightly convoluted because starting the transition
+        // requires the handler, but the mixed handler also needs a reference to the transition.
         RecentsMixedHandler mixer = null;
         Consumer<IBinder> setTransitionForMixer = null;
         for (int i = 0; i < mMixers.size(); ++i) {
@@ -160,12 +219,11 @@
         }
         final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
                 mixer == null ? this : mixer);
-        for (int i = 0; i < mStateListeners.size(); i++) {
-            mStateListeners.get(i).onTransitionStarted(transition);
-        }
         if (mixer != null) {
             setTransitionForMixer.accept(transition);
         }
+
+        final RecentsController controller = new RecentsController(listener);
         if (transition != null) {
             controller.setTransition(transition);
             mControllers.add(controller);
@@ -187,11 +245,28 @@
         return null;
     }
 
-    private int findController(IBinder transition) {
+    /**
+     * Returns if there is currently a pending or active recents transition.
+     */
+    @Nullable
+    private RecentsController getLastController() {
+        return !mControllers.isEmpty() ? mControllers.getLast() : null;
+    }
+
+    /**
+     * Finds an existing controller for the provided {@param transition}, or {@code null} if none
+     * exists.
+     */
+    @Nullable
+    @VisibleForTesting
+    RecentsController findController(@NonNull IBinder transition) {
         for (int i = mControllers.size() - 1; i >= 0; --i) {
-            if (mControllers.get(i).mTransition == transition) return i;
+            final RecentsController controller = mControllers.get(i);
+            if (controller.mTransition == transition) {
+                return controller;
+            }
         }
-        return -1;
+        return null;
     }
 
     @Override
@@ -199,13 +274,12 @@
             SurfaceControl.Transaction startTransaction,
             SurfaceControl.Transaction finishTransaction,
             Transitions.TransitionFinishCallback finishCallback) {
-        final int controllerIdx = findController(transition);
-        if (controllerIdx < 0) {
+        final RecentsController controller = findController(transition);
+        if (controller == null) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "RecentsTransitionHandler.startAnimation: no controller found");
             return false;
         }
-        final RecentsController controller = mControllers.get(controllerIdx);
         final IApplicationThread animApp = mAnimApp;
         mAnimApp = null;
         if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
@@ -221,13 +295,12 @@
     public void mergeAnimation(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction t, IBinder mergeTarget,
             Transitions.TransitionFinishCallback finishCallback) {
-        final int targetIdx = findController(mergeTarget);
-        if (targetIdx < 0) {
+        final RecentsController controller = findController(mergeTarget);
+        if (controller == null) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "RecentsTransitionHandler.mergeAnimation: no controller found");
             return;
         }
-        final RecentsController controller = mControllers.get(targetIdx);
         controller.merge(info, t, finishCallback);
     }
 
@@ -244,8 +317,21 @@
         }
     }
 
+    @Override
+    public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        RecentsController controller = findController(SYNTHETIC_TRANSITION);
+        if (controller != null) {
+            // Cancel the existing synthetic transition if there is one
+            controller.cancel("incoming_transition");
+        }
+    }
+
     /** There is only one of these and it gets reset on finish. */
-    private class RecentsController extends IRecentsAnimationController.Stub {
+    @VisibleForTesting
+    class RecentsController extends IRecentsAnimationController.Stub {
+
         private final int mInstanceId;
 
         private IRecentsAnimationRunner mListener;
@@ -307,7 +393,8 @@
             mDeathHandler = () -> {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                         "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
-                finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
+                finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+                        "deathRecipient");
             };
             try {
                 mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -317,6 +404,9 @@
             }
         }
 
+        /**
+         * Sets the started transition for this instance of the recents transition.
+         */
         void setTransition(IBinder transition) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
@@ -330,6 +420,10 @@
         }
 
         void cancel(boolean toHome, boolean withScreenshots, String reason) {
+            if (cancelSyntheticTransition(reason)) {
+                return;
+            }
+
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "[%d] RecentsController.cancel: toHome=%b reason=%s",
                     mInstanceId, toHome, reason);
@@ -341,7 +435,7 @@
                 }
             }
             if (mFinishCB != null) {
-                finishInner(toHome, false /* userLeave */, null /* finishCb */);
+                finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
             } else {
                 cleanUp();
             }
@@ -436,6 +530,91 @@
             }
         }
 
+        /**
+         * Starts a new transition that is not backed by a system transition.
+         */
+        void startSyntheticTransition() {
+            mTransition = SYNTHETIC_TRANSITION;
+
+            // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
+            //                    both opening and closing since there's some pre-existing
+            //                    dependencies on having a closing task
+            final ActivityManager.RunningTaskInfo homeTask =
+                    mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
+                            .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
+                            .findFirst()
+                            .get();
+            final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
+                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+                    0, true /* isTranslucent */);
+            final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
+                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+                    0, true /* isTranslucent */);
+            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+            apps.add(openingTarget);
+            apps.add(closingTarget);
+            try {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                        "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+                        mInstanceId, apps.size());
+                mListener.onAnimationStart(this,
+                        apps.toArray(new RemoteAnimationTarget[apps.size()]),
+                        new RemoteAnimationTarget[0],
+                        new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+                for (int i = 0; i < mStateListeners.size(); i++) {
+                    mStateListeners.get(i).onAnimationStateChanged(true);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error starting recents animation", e);
+                cancel("startSynthetricTransition() failed");
+            }
+        }
+
+        /**
+         * Returns whether this transition is backed by a real system transition or not.
+         */
+        boolean isSyntheticTransition() {
+            return mTransition == SYNTHETIC_TRANSITION;
+        }
+
+        /**
+         * Called when a synthetic transition is canceled.
+         */
+        boolean cancelSyntheticTransition(String reason) {
+            if (!isSyntheticTransition()) {
+                return false;
+            }
+
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                    "[%d] RecentsController.cancelSyntheticTransition reason=%s",
+                    mInstanceId, reason);
+            try {
+                // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
+                //                    clean up leashes accordingly
+                mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error canceling previous recents animation", e);
+            }
+            cleanUp();
+            return true;
+        }
+
+        /**
+         * Called when a synthetic transition is finished.
+         * @return
+         */
+        boolean finishSyntheticTransition() {
+            if (!isSyntheticTransition()) {
+                return false;
+            }
+
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                    "[%d] RecentsController.finishSyntheticTransition", mInstanceId);
+            // TODO(b/366021931): Clean up leashes accordingly
+            cleanUp();
+            return true;
+        }
+
         boolean start(TransitionInfo info, SurfaceControl.Transaction t,
                 SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -662,7 +841,7 @@
                             // Set the callback once again so we can finish correctly.
                             mFinishCB = finishCB;
                             finishInner(true /* toHome */, false /* userLeave */,
-                                    null /* finishCb */);
+                                    null /* finishCb */, "takeOverAnimation");
                         }, updatedStates);
             });
         }
@@ -810,7 +989,7 @@
                 sendCancelWithSnapshots();
                 mExecutor.executeDelayed(
                         () -> finishInner(true /* toHome */, false /* userLeaveHint */,
-                                null /* finishCb */), 0);
+                                null /* finishCb */, "merge"), 0);
                 return;
             }
             if (recentsOpening != null) {
@@ -1005,7 +1184,7 @@
                     return;
                 }
                 final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
-                        : Display.DEFAULT_DISPLAY;
+                        : DEFAULT_DISPLAY;
                 // transient launches don't receive focus automatically. Since we are taking over
                 // the gesture now, take focus explicitly.
                 // This also moves recents back to top if the user gestured before a switch
@@ -1038,11 +1217,16 @@
         @Override
         @SuppressLint("NewApi")
         public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
-            mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
+            mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
+                    "requested"));
         }
 
         private void finishInner(boolean toHome, boolean sendUserLeaveHint,
-                IResultReceiver runnerFinishCb) {
+                IResultReceiver runnerFinishCb, String reason) {
+            if (finishSyntheticTransition()) {
+                return;
+            }
+
             if (mFinishCB == null) {
                 Slog.e(TAG, "Duplicate call to finish");
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index e8733eb..95874c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -24,7 +24,4 @@
     /** Notifies whether the recents animation is running. */
     default void onAnimationStateChanged(boolean running) {
     }
-
-    /** Notifies that a recents shell transition has started. */
-    default void onTransitionStarted(IBinder transition) {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4ba84ee..5a905cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1654,7 +1654,7 @@
             mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
                     mRootTaskInfo.configuration, this, mParentContainerCallbacks,
                     mDisplayController, mDisplayImeController, mTaskOrganizer,
-                    PARALLAX_ALIGN_CENTER /* parallaxType */);
+                    PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler);
             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
         }
 
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 2c02d4f..d03832d 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
@@ -1501,16 +1501,16 @@
          *                          transition animation. The Transition system will apply it when
          *                          finishCallback is called by the transition handler.
          */
-        void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+        default void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction);
+                @NonNull SurfaceControl.Transaction finishTransaction) {}
 
         /**
          * Called when the transition is starting to play. It isn't called for merged transitions.
          *
          * @param transition the unique token of this transition
          */
-        void onTransitionStarting(@NonNull IBinder transition);
+        default void onTransitionStarting(@NonNull IBinder transition) {}
 
         /**
          * Called when a transition is merged into another transition. There won't be any following
@@ -1519,7 +1519,7 @@
          * @param merged the unique token of the transition that's merged to another one
          * @param playing the unique token of the transition that accepts the merge
          */
-        void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+        default void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
 
         /**
          * Called when the transition is finished. This isn't called for merged transitions.
@@ -1527,7 +1527,7 @@
          * @param transition the unique token of this transition
          * @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
          */
-        void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+        default void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
     }
 
     @BinderThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b14283f..02c818f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -111,6 +111,7 @@
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -154,7 +155,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellController mShellController;
     private final Context mContext;
-    private final Handler mMainHandler;
+    private final @ShellMainThread Handler mMainHandler;
     private final @ShellBackgroundThread ShellExecutor mBgExecutor;
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
@@ -214,7 +215,7 @@
     public DesktopModeWindowDecorViewModel(
             Context context,
             ShellExecutor shellExecutor,
-            Handler mainHandler,
+            @ShellMainThread Handler mainHandler,
             Choreographer mainChoreographer,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             ShellInit shellInit,
@@ -270,7 +271,7 @@
     DesktopModeWindowDecorViewModel(
             Context context,
             ShellExecutor shellExecutor,
-            Handler mainHandler,
+            @ShellMainThread Handler mainHandler,
             Choreographer mainChoreographer,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             ShellInit shellInit,
@@ -495,7 +496,8 @@
             return;
         }
         mInteractionJankMonitor.begin(
-                decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
+                decoration.mTaskSurface, mContext, mMainHandler,
+                Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
         mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
         decoration.closeHandleMenu();
         decoration.closeMaximizeMenu();
@@ -512,7 +514,7 @@
             Toast.makeText(mContext,
                     R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
         } else {
-            mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+            mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
                     Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
             mDesktopTasksController.snapToHalfScreen(
                     decoration.mTaskInfo,
@@ -548,7 +550,7 @@
             return;
         }
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+        mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
                 CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
         // App sometimes draws before the insets from WindowDecoration#relayout have
         // been added, so they must be added here
@@ -860,6 +862,15 @@
                 handleCaptionThroughStatusBar(e, decoration);
                 final boolean wasDragging = mIsDragging;
                 updateDragStatus(e.getActionMasked());
+                final boolean upOrCancel = e.getActionMasked() == ACTION_UP
+                        || e.getActionMasked() == ACTION_CANCEL;
+                if (wasDragging && upOrCancel) {
+                    // When finishing a drag the event will be consumed, which means the pressed
+                    // state of the App Handle must be manually reset to scale its drawable back to
+                    // its original shape. This is necessary for drag gestures of the Handle that
+                    // result in a cancellation (dragging back to the top).
+                    v.setPressed(false);
+                }
                 // Only prevent onClick from receiving this event if it's a drag.
                 return wasDragging;
             }
@@ -1378,7 +1389,8 @@
                 mDragStartListener,
                 mTransitions,
                 mInteractionJankMonitor,
-                mTransactionFactory);
+                mTransactionFactory,
+                mMainHandler);
         windowDecoration.setTaskDragResizer(taskPositioner);
 
         final DesktopModeTouchEventListener touchEventListener =
@@ -1602,7 +1614,8 @@
                 DragPositioningCallbackUtility.DragStartListener dragStartListener,
                 Transitions transitions,
                 InteractionJankMonitor interactionJankMonitor,
-                Supplier<SurfaceControl.Transaction> transactionFactory) {
+                Supplier<SurfaceControl.Transaction> transactionFactory,
+                Handler handler) {
             final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
                     ? new VeiledResizeTaskPositioner(
                             taskOrganizer,
@@ -1610,7 +1623,8 @@
                             displayController,
                             dragStartListener,
                             transitions,
-                            interactionJankMonitor)
+                            interactionJankMonitor,
+                            handler)
                     : new FluidResizeTaskPositioner(
                             taskOrganizer,
                             transitions,
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 d43ee44..16036be 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
@@ -24,6 +24,8 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
 import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
@@ -604,13 +606,13 @@
                 // their custom content.
                 relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
             } else {
-                if (Flags.enableCaptionCompatInsetForceConsumption()) {
+                if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
                     // Force-consume the caption bar insets when the app tries to hide the caption.
                     // This improves app compatibility of immersive apps.
                     relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
                 }
             }
-            if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) {
+            if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()) {
                 // Always force-consume the caption bar insets for maximum app compatibility,
                 // including non-immersive apps that just don't handle caption insets properly.
                 relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
@@ -753,9 +755,12 @@
             final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
             final IconProvider provider = new IconProvider(mContext);
             final Drawable appIconDrawable = provider.getIcon(activityInfo);
+            final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
+                    UserHandle.of(mTaskInfo.userId));
             final BaseIconFactory headerIconFactory = createIconFactory(mContext,
                     R.dimen.desktop_mode_caption_icon_radius);
-            mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+            mAppIconBitmap = headerIconFactory.createIconBitmap(badgedAppIconDrawable,
+                    1f /* scale */);
 
             final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
                     R.dimen.desktop_mode_resize_veil_icon_size);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5998155..6f3f411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -24,6 +24,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.IBinder;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -37,6 +38,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.function.Supplier;
@@ -63,14 +65,17 @@
     private int mCtrlType;
     private boolean mIsResizingOrAnimatingResize;
     @Surface.Rotation private int mRotation;
+    @ShellMainThread
+    private final Handler mHandler;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration,
             DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
+            Transitions transitions, InteractionJankMonitor interactionJankMonitor,
+            @ShellMainThread Handler handler) {
         this(taskOrganizer, windowDecoration, displayController, dragStartListener,
-                SurfaceControl.Transaction::new, transitions, interactionJankMonitor);
+                SurfaceControl.Transaction::new, transitions, interactionJankMonitor, handler);
     }
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -78,7 +83,7 @@
             DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
             Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) {
         mDesktopWindowDecoration = windowDecoration;
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
@@ -86,6 +91,7 @@
         mTransactionSupplier = supplier;
         mTransitions = transitions;
         mInteractionJankMonitor = interactionJankMonitor;
+        mHandler = handler;
     }
 
     @Override
@@ -97,7 +103,7 @@
         if (isResizing()) {
             // Capture CUJ for re-sizing window in DW mode.
             mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
-                    mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+                    mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
             if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
                 WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -131,7 +137,7 @@
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             // Begin window drag CUJ instrumentation only when drag position moves.
             mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
-                    mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+                    mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
                     mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index b53ea38..227060d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -137,6 +137,8 @@
     private Transitions mTransitions;
     @Mock
     private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    @Mock
+    private Handler mHandler;
 
     private BackAnimationController mController;
     private TestableContentResolver mContentResolver;
@@ -161,13 +163,14 @@
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
         mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
-                mAnimationBackground, mRootTaskDisplayAreaOrganizer);
-        mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
+                mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler);
+        mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground,
+                mHandler);
         mShellBackAnimationRegistry =
                 new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation,
                         mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null,
                         new CustomCrossActivityBackAnimation(mContext, mAnimationBackground,
-                                mRootTaskDisplayAreaOrganizer),
+                                mRootTaskDisplayAreaOrganizer, mHandler),
                         /* defaultBackToHomeAnimation= */ null);
         mController =
                 new BackAnimationController(
@@ -181,7 +184,8 @@
                         mAnimationBackground,
                         mShellBackAnimationRegistry,
                         mShellCommandHandler,
-                        mTransitions);
+                        mTransitions,
+                        mHandler);
         mShellInit.init();
         mShellExecutor.flushAll();
         mTouchableRegion = new Rect(0, 0, 100, 100);
@@ -344,7 +348,8 @@
                         mAnimationBackground,
                         mShellBackAnimationRegistry,
                         mShellCommandHandler,
-                        mTransitions);
+                        mTransitions,
+                        mHandler);
         shellInit.init();
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
@@ -898,7 +903,8 @@
                 new BackAnimationRunner(
                         mAnimatorCallback,
                         mBackAnimationRunner,
-                        mContext));
+                        mContext,
+                        mHandler));
     }
 
     private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 080ad90..5b5ef6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -22,6 +22,7 @@
 import android.graphics.Color
 import android.graphics.Point
 import android.graphics.Rect
+import android.os.Handler
 import android.os.RemoteException
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -66,6 +67,7 @@
     @Mock private lateinit var transitionAnimation: TransitionAnimation
     @Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo
     @Mock private lateinit var transaction: Transaction
+    @Mock private lateinit var handler: Handler
 
     private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation
     private lateinit var customAnimationLoader: CustomAnimationLoader
@@ -80,7 +82,8 @@
                 backAnimationBackground,
                 rootTaskDisplayAreaOrganizer,
                 transaction,
-                customAnimationLoader
+                customAnimationLoader,
+                handler,
             )
 
         whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID)))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index f5847cc..cf69704 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -25,6 +25,7 @@
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.view.InputDevice;
@@ -55,6 +56,7 @@
     private @Mock DisplayController mDisplayController;
     private @Mock DisplayImeController mDisplayImeController;
     private @Mock ShellTaskOrganizer mTaskOrganizer;
+    private @Mock Handler mHandler;
     private SplitLayout mSplitLayout;
     private DividerView mDividerView;
 
@@ -65,7 +67,7 @@
         Configuration configuration = getConfiguration();
         mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
                 mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
-                mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+                mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler);
         SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
                 mContext,
                 configuration, mCallbacks);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 82b3a7d..177e47a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -35,6 +35,7 @@
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.annotation.UiThreadTest;
@@ -65,6 +66,7 @@
     @Mock DisplayImeController mDisplayImeController;
     @Mock ShellTaskOrganizer mTaskOrganizer;
     @Mock WindowContainerTransaction mWct;
+    @Mock Handler mHandler;
     @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
     private SplitLayout mSplitLayout;
 
@@ -80,7 +82,8 @@
                 mDisplayController,
                 mDisplayImeController,
                 mTaskOrganizer,
-                SplitLayout.PARALLAX_NONE));
+                SplitLayout.PARALLAX_NONE,
+                mHandler));
     }
 
     @Test
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 b47201e..e610ebd 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
@@ -40,6 +40,7 @@
 import android.graphics.PointF
 import android.graphics.Rect
 import android.os.Binder
+import android.os.Handler
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
@@ -181,6 +182,7 @@
   private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
   @Mock private lateinit var mockSurface: SurfaceControl
   @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
+  @Mock private lateinit var mockHandler: Handler
 
   private lateinit var mockitoSession: StaticMockitoSession
   private lateinit var controller: DesktopTasksController
@@ -221,7 +223,8 @@
             shellTaskOrganizer,
             MAX_TASK_LIMIT,
             mockInteractionJankMonitor,
-            mContext)
+            mContext,
+            mockHandler)
 
     whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
     whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -274,7 +277,9 @@
         shellExecutor,
         Optional.of(desktopTasksLimiter),
         recentTasksController,
-        mockInteractionJankMonitor)
+        mockInteractionJankMonitor,
+        mockHandler,
+      )
   }
 
   @After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 2d0e428..61d03ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.os.Binder
+import android.os.Handler
 import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
@@ -70,6 +71,7 @@
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock lateinit var handler: Handler
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var desktopTasksLimiter: DesktopTasksLimiter
@@ -85,7 +87,7 @@
 
         desktopTasksLimiter =
             DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
-                interactionJankMonitor, mContext)
+                interactionJankMonitor, mContext, handler)
     }
 
     @After
@@ -97,7 +99,7 @@
     fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
         assertFailsWith<IllegalArgumentException> {
             DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0,
-                interactionJankMonitor, mContext)
+                interactionJankMonitor, mContext, handler)
         }
     }
 
@@ -105,7 +107,7 @@
     fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
         assertFailsWith<IllegalArgumentException> {
             DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5,
-                interactionJankMonitor, mContext)
+                interactionJankMonitor, mContext, handler)
         }
     }
 
@@ -334,7 +336,7 @@
     fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() {
         desktopTasksLimiter =
             DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2,
-                interactionJankMonitor, mContext)
+                interactionJankMonitor, mContext, handler)
         val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
 
         val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
@@ -375,6 +377,7 @@
         verify(interactionJankMonitor).begin(
             any(),
             eq(mContext),
+            eq(handler),
             eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
 
         desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
@@ -403,7 +406,9 @@
         verify(interactionJankMonitor).begin(
             any(),
             eq(mContext),
-            eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
+            eq(handler),
+            eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW),
+        )
 
         desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
             transition,
@@ -432,6 +437,7 @@
         verify(interactionJankMonitor).begin(
             any(),
             eq(mContext),
+            eq(handler),
             eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
 
         desktopTasksLimiter.getTransitionObserver().onTransitionMerged(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e0463b4..fefa933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.os.Handler;
 import android.os.IBinder;
 import android.util.DisplayMetrics;
 import android.view.SurfaceControl;
@@ -81,6 +82,8 @@
     Transitions.TransitionFinishCallback mTransitionFinishCallback;
     @Mock
     ShellExecutor mExecutor;
+    @Mock
+    Handler mHandler;
 
     private Point mPoint;
     private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
@@ -97,7 +100,7 @@
                 .thenReturn(getContext().getResources().getDisplayMetrics());
 
         mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
-                mContext, mInteractionJankMonitor);
+                mContext, mInteractionJankMonitor, mHandler);
         mPoint = new Point(0, 0);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 9c7f723..9146906 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -37,6 +37,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
@@ -100,6 +101,8 @@
     OneHandedSettingsUtil mMockSettingsUitl;
     @Mock
     InteractionJankMonitor mJankMonitor;
+    @Mock
+    Handler mMockHandler;
 
     List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>();
 
@@ -142,7 +145,8 @@
                 mMockAnimationController,
                 mTutorialHandler,
                 mJankMonitor,
-                mMockShellMainExecutor));
+                mMockShellMainExecutor,
+                mMockHandler));
 
         for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) {
             mDisplayAreaAppearedInfoList.add(getDummyDisplayAreaInfo());
@@ -429,7 +433,8 @@
                         mMockAnimationController,
                         mTutorialHandler,
                         mJankMonitor,
-                        mMockShellMainExecutor));
+                        mMockShellMainExecutor,
+                        mMockHandler));
 
         assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f3944d5..9600351 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -40,6 +40,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -115,6 +116,7 @@
     @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
     @Mock private DisplayInsetsController mMockDisplayInsetsController;
     @Mock private TabletopModeController mMockTabletopModeController;
+    @Mock private Handler mMockHandler;
 
     @Mock private DisplayLayout mMockDisplayLayout1;
     @Mock private DisplayLayout mMockDisplayLayout2;
@@ -138,7 +140,7 @@
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
                 mMockDisplayInsetsController, mMockTabletopModeController,
-                mMockOneHandedController, mMockExecutor);
+                mMockOneHandedController, mMockExecutor, mMockHandler);
         mShellInit.init();
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -230,7 +232,7 @@
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
                 mMockDisplayInsetsController, mMockTabletopModeController,
-                mMockOneHandedController, mMockExecutor));
+                mMockOneHandedController, mMockExecutor, mMockHandler));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
new file mode 100644
index 0000000..769acf7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.recents;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
+import com.android.wm.shell.transition.Transitions;
+
+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.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link RecentTasksController}
+ *
+ * Usage: atest WMShellUnitTests:RecentsTransitionHandlerTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecentsTransitionHandlerTest extends ShellTestCase {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private TaskStackListenerImpl mTaskStackListener;
+    @Mock
+    private ShellCommandHandler mShellCommandHandler;
+    @Mock
+    private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    @Mock
+    private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private DisplayInsetsController mDisplayInsetsController;
+    @Mock
+    private IRecentTasksListener mRecentTasksListener;
+    @Mock
+    private TaskStackTransitionObserver mTaskStackTransitionObserver;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private ShellTaskOrganizer mShellTaskOrganizer;
+    private RecentTasksController mRecentTasksController;
+    private RecentTasksController mRecentTasksControllerReal;
+    private RecentsTransitionHandler mRecentsTransitionHandler;
+    private ShellInit mShellInit;
+    private ShellController mShellController;
+    private TestShellExecutor mMainExecutor;
+    private static StaticMockitoSession sMockitoSession;
+
+    @Before
+    public void setUp() {
+        sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
+                .mockStatic(DesktopModeStatus.class).startMocking();
+        ExtendedMockito.doReturn(true)
+                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+        mMainExecutor = new TestShellExecutor();
+        when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+        when(mContext.getSystemService(KeyguardManager.class))
+                .thenReturn(mock(KeyguardManager.class));
+        mShellInit = spy(new ShellInit(mMainExecutor));
+        mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
+                mDisplayInsetsController, mMainExecutor));
+        mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
+                mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+                Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+                mMainExecutor);
+        mRecentTasksController = spy(mRecentTasksControllerReal);
+        mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+                null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+                mMainExecutor);
+
+        final Transitions transitions = mock(Transitions.class);
+        doReturn(mMainExecutor).when(transitions).getMainExecutor();
+        mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer,
+                transitions, mRecentTasksController, mock(HomeTransitionObserver.class));
+
+        mShellInit.init();
+    }
+
+    @After
+    public void tearDown() {
+        sMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testStartSyntheticRecentsTransition_callsOnAnimationStart() throws Exception {
+        final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+        doReturn(new Binder()).when(runner).asBinder();
+        Bundle options = new Bundle();
+        options.putBoolean("is_synthetic_recents_transition", true);
+        IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+                mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+                runner);
+        verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+        // Finish and verify no transition remains
+        mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
+                false /* sendUserLeaveHint */, null /* finishCb */);
+        mMainExecutor.flushAll();
+        assertNull(mRecentsTransitionHandler.findController(transition));
+    }
+
+    @Test
+    public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception {
+        final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+        doReturn(new Binder()).when(runner).asBinder();
+        Bundle options = new Bundle();
+        options.putBoolean("is_synthetic_recents_transition", true);
+        IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+                mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+                runner);
+        verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+        mRecentsTransitionHandler.findController(transition).cancel("test");
+        mMainExecutor.flushAll();
+        verify(runner).onAnimationCanceled(any(), any());
+        assertNull(mRecentsTransitionHandler.findController(transition));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 61a725f..fec9e3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1211,7 +1211,7 @@
                         mTransactionPool, createTestDisplayController(), mMainExecutor,
                         mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
         final RecentsTransitionHandler recentsHandler =
-                new RecentsTransitionHandler(shellInit, transitions,
+                new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
                         mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
         shellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index a18fbf0..85bc7cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -247,7 +247,18 @@
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
         whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
-        whenever(mockTaskPositionerFactory.create(any(), any(), any(), any(), any(), any(), any()))
+        whenever(
+            mockTaskPositionerFactory.create(
+                any(),
+                any(),
+                any(),
+                any(),
+                any(),
+                any(),
+                any(),
+                any()
+            )
+        )
             .thenReturn(mockTaskPositioner)
 
         doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 7784af6..ab41d9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,7 @@
 import android.content.res.Resources
 import android.graphics.Point
 import android.graphics.Rect
+import android.os.Handler
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.view.Display
@@ -107,6 +108,8 @@
     private lateinit var mockResources: Resources
     @Mock
     private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    @Mock
+    private lateinit var mockHandler: Handler
 
     private lateinit var taskPositioner: VeiledResizeTaskPositioner
 
@@ -155,7 +158,8 @@
                         mockDragStartListener,
                         mockTransactionFactory,
                         mockTransitions,
-                        mockInteractionJankMonitor
+                        mockInteractionJankMonitor,
+                        mockHandler,
                 )
     }
 
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index d7bf201..e13e1365 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -73,6 +73,7 @@
     }
     paint->setStrokeJoin(SkPaint::kRound_Join);
     paint->setLooper(nullptr);
+    paint->setBlendMode(SkBlendMode::kSrcOver);
 }
 
 class DrawTextFunctor {
diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h
index d709430..b4e7bd3 100644
--- a/libs/hwui/platform/host/thread/ThreadBase.h
+++ b/libs/hwui/platform/host/thread/ThreadBase.h
@@ -48,7 +48,7 @@
         nsecs_t nextWakeup = mQueue.nextWakeup(lock);
         std::chrono::nanoseconds duration = std::chrono::nanoseconds::max();
         if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
-            int timeout = nextWakeup - WorkQueue::clock::now();
+            nsecs_t timeout = nextWakeup - WorkQueue::clock::now();
             if (timeout < 0) timeout = 0;
             duration = std::chrono::nanoseconds(timeout);
         }
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 1afef75..d993b87 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -64,25 +64,6 @@
     mLocked.pointerSprite.clear();
 }
 
-std::optional<FloatRect> MouseCursorController::getBounds() const {
-    std::scoped_lock lock(mLock);
-
-    return getBoundsLocked();
-}
-
-std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
-    if (!mLocked.viewport.isValid()) {
-        return {};
-    }
-
-    return FloatRect{
-            static_cast<float>(mLocked.viewport.logicalLeft),
-            static_cast<float>(mLocked.viewport.logicalTop),
-            static_cast<float>(mLocked.viewport.logicalRight - 1),
-            static_cast<float>(mLocked.viewport.logicalBottom - 1),
-    };
-}
-
 void MouseCursorController::move(float deltaX, float deltaY) {
 #if DEBUG_MOUSE_CURSOR_UPDATES
     ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
@@ -105,11 +86,20 @@
 }
 
 void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
-    const auto bounds = getBoundsLocked();
-    if (!bounds) return;
+    const auto& v = mLocked.viewport;
+    if (!v.isValid()) return;
 
-    mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
-    mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+    // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside
+    // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily
+    // close to the outside edge.
+    const FloatRect bounds{
+            static_cast<float>(mLocked.viewport.logicalLeft),
+            static_cast<float>(mLocked.viewport.logicalTop),
+            static_cast<float>(mLocked.viewport.logicalRight - 1),
+            static_cast<float>(mLocked.viewport.logicalBottom - 1),
+    };
+    mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x));
+    mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y));
 
     updatePointerLocked();
 }
@@ -216,9 +206,11 @@
     // Reset cursor position to center if size or display changed.
     if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
         oldDisplayHeight != newDisplayHeight) {
-        if (const auto bounds = getBoundsLocked(); bounds) {
-            mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
-            mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
+        if (viewport.isValid()) {
+            // Use integer coordinates as the starting point for the cursor location.
+            // We usually expect display sizes to be even numbers, so the flooring is precautionary.
+            mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2);
+            mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2);
             // Reload icon resources for density may be changed.
             loadResourcesLocked(getAdditionalMouseResources);
         } else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 8600341..12b31a8 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,7 +43,6 @@
     MouseCursorController(PointerControllerContext& context);
     ~MouseCursorController();
 
-    std::optional<FloatRect> getBounds() const;
     void move(float deltaX, float deltaY);
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
@@ -104,7 +103,6 @@
 
     } mLocked GUARDED_BY(mLock);
 
-    std::optional<FloatRect> getBoundsLocked() const;
     void setPositionLocked(float x, float y);
 
     void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 5ae967b..78d7d3a 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -138,10 +138,6 @@
     return mDisplayInfoListener->mLock;
 }
 
-std::optional<FloatRect> PointerController::getBounds() const {
-    return mCursorController.getBounds();
-}
-
 void PointerController::move(float deltaX, float deltaY) {
     const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     vec2 transformed;
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 4d1e1d7..ee8d121 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -51,7 +51,6 @@
 
     ~PointerController() override;
 
-    std::optional<FloatRect> getBounds() const override;
     void move(float deltaX, float deltaY) override;
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
@@ -166,9 +165,6 @@
 
     ~TouchPointerController() override;
 
-    std::optional<FloatRect> getBounds() const override {
-        LOG_ALWAYS_FATAL("Should not be called");
-    }
     void move(float, float) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index dcf5c5b..cddc337 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -111,6 +111,17 @@
 }
 
 flag {
+    name: "enable_ni_supl_message_injection_by_carrier_config_bugfix"
+    namespace: "location"
+    description: "Flag for enabling NI SUPL message injection by carrier config"
+    bug: "242105192"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_ni_supl_message_injection_by_carrier_config"
     namespace: "location"
     description: "Flag for enabling NI SUPL message injection by carrier config"
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e2e7a46..cdb517b3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6318,7 +6318,14 @@
     /**
      * @hide
      * Get the audio devices that would be used for the routing of the given audio attributes.
-     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried.
+     *   For queries about output devices (playback use cases), a valid usage must be specified in
+     *   the audio attributes via AudioAttributes.Builder.setUsage(). The capture preset MUST NOT
+     *   be changed from default.
+     *   For queries about input devices (capture use case), a valid capture preset MUST be
+     *   specified in the audio attributes via AudioAttributes.Builder.setCapturePreset(). If a
+     *   capture preset is present, then this has precedence over any usage or content type also
+     *   present in the audio attrirutes.
      * @return an empty list if there was an issue with the request, a list of audio devices
      *   otherwise (typically one device, except for duplicated paths).
      */
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3cc0ad2..31f8996 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -90,24 +90,24 @@
         mDisplayManager = displayManager;
     }
 
-  /**
-   * Register a listener to receive notifications about when the {@link MediaProjection} or captured
-   * content changes state.
-   *
-   * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
-   * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications on
-   * the callback are not missed. The client must implement {@link Callback#onStop()} and clean up
-   * any resources it is holding, e.g. the {@link VirtualDisplay} and {@link Surface}. This should
-   * also update any application UI indicating the MediaProjection status as MediaProjection has
-   * stopped.
-   *
-   * @param callback The callback to call.
-   * @param handler The handler on which the callback should be invoked, or null if the callback
-   *     should be invoked on the calling thread's looper.
-   * @throws NullPointerException If the given callback is null.
-   * @see #unregisterCallback
-   */
-  public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+    /**
+     * Register a listener to receive notifications about when the {@link MediaProjection} or
+     * captured content changes state.
+     *
+     * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
+     * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications
+     * on the callback are not missed. The client must implement {@link Callback#onStop()} to
+     * properly handle MediaProjection clean up any resources it is holding, e.g. the {@link
+     * VirtualDisplay} and {@link Surface}. This should also update any application UI indicating
+     * the MediaProjection status as MediaProjection has stopped.
+     *
+     * @param callback The callback to call.
+     * @param handler The handler on which the callback should be invoked, or null if the callback
+     *     should be invoked on the calling thread's looper.
+     * @throws NullPointerException If the given callback is null.
+     * @see #unregisterCallback
+     */
+    public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
         try {
             final Callback c = Objects.requireNonNull(callback);
             if (handler == null) {
@@ -313,7 +313,7 @@
      */
     public abstract static class Callback {
         /**
-         * Called when the MediaProjection session is no longer valid.
+         * Called when the MediaProjection session has been stopped and is no longer valid.
          *
          * <p>Once a MediaProjection has been stopped, it's up to the application to release any
          * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and {@link
@@ -321,9 +321,9 @@
          * it should be updated to indicate that MediaProjection is no longer active.
          *
          * <p>MediaProjection stopping can be a result of the system stopping the ongoing
-         * MediaProjection due to various reasons, such as another MediaProjection session starting.
-         * MediaProjection may also stop due to the user explicitly stopping ongoing MediaProjection
-         * via any available system-level UI.
+         * MediaProjection due to various reasons, such as another MediaProjection session starting,
+         * a user stopping the session via UI affordances in system-level UI, or the screen being
+         * locked.
          *
          * <p>After this callback any call to {@link MediaProjection#createVirtualDisplay} will
          * fail, even if no such {@link VirtualDisplay} was ever created for this MediaProjection
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 03fd2c6..dc55e41 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -60,13 +60,14 @@
  *   <li>Register a {@link MediaProjection.Callback} by calling {@link
  *       MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
  *       receive notifications about when the {@link MediaProjection} or captured content changes
- *       state. When receiving an `onStop()` callback, the client must clean up any resources it is
- *       holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
- *       further no longer create any new {@link VirtualDisplay}s via {@link
- *       MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- *       VirtualDisplay.Callback, Handler)}. Note that the `onStop()` callback can be a result of
- *       the system stopping MediaProjection due to various reasons or the user stopping the
- *       MediaProjection via UI affordances in system-level UI.
+ *       state. When receiving an `onStop()` callback the {@link MediaProjection} session has been
+ *       finished and the client must clean up any resources it is holding, e.g. the {@link
+ *       VirtualDisplay} and {@link Surface}. The MediaProjection may further no longer create any
+ *       new {@link VirtualDisplay}s via {@link MediaProjection#createVirtualDisplay(String, int,
+ *       int, int, int, Surface, VirtualDisplay.Callback, Handler)}. Note that the `onStop()`
+ *       callback can be a result of the system stopping MediaProjection due to various reasons.
+ *       This includes the user stopping the MediaProjection via UI affordances in system-level UI,
+ *       the screen being locked, or another {@link MediaProjection} session starting.
  *   <li>Start the screen capture session for media projection by calling {@link
  *       MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
  *       android.hardware.display.VirtualDisplay.Callback, Handler)}.
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index feee89a..34e33c0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1409,6 +1409,8 @@
     <string name="media_transfer_this_device_name">This phone</string>
     <!-- Name of the tablet device. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_device_name_tablet">This tablet</string>
+    <!-- Name of the internal speaker. [CHAR LIMIT=30] -->
+    <string name="media_transfer_this_device_name_desktop">This computer (internal)</string>
     <!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
     <!-- Name of the internal mic. [CHAR LIMIT=30] -->
@@ -1637,7 +1639,13 @@
     <string name="cached_apps_freezer_reboot_dialog_text">Your device must be rebooted for this change to apply. Reboot now or cancel.</string>
 
     <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] -->
-    <string name="media_transfer_wired_usb_device_name">Wired headphone</string>
+    <string name="media_transfer_wired_headphone_name">Wired headphone</string>
+
+    <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] -->
+    <string name="media_transfer_headphone_name">Headphone</string>
+
+    <!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] -->
+    <string name="media_transfer_usb_speaker_name">USB speaker</string>
 
     <!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
     <string name="media_transfer_wired_device_mic_name">Mic jack</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 148e164..c9f9d1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -151,7 +151,19 @@
             Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
             return;
         }
-        mService.addSource(sink, metadata, isGroupOp);
+        try {
+            mService.addSource(sink, metadata, isGroupOp);
+        } catch (IllegalStateException e) {
+            // BT will check callback registration before add source.
+            // If it throw callback exception when bt is disabled, then the failure is intended,
+            // just catch it here.
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (adapter != null && adapter.isEnabled()) {
+                throw e;
+            } else {
+                Log.d(TAG, "Catch addSource failure when bt is disabled: " + e);
+            }
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 9eaf8d3..0b8fb22ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -72,6 +72,8 @@
             return context.getString(R.string.media_transfer_this_device_name_tv);
         } else if (isTablet()) {
             return context.getString(R.string.media_transfer_this_device_name_tablet);
+        } else if (inputRoutingEnabledAndIsDesktop()) {
+            return context.getString(R.string.media_transfer_this_device_name_desktop);
         } else {
             return context.getString(R.string.media_transfer_this_device_name);
         }
@@ -85,10 +87,18 @@
         switch (routeInfo.getType()) {
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
+                name =
+                        inputRoutingEnabledAndIsDesktop()
+                                ? context.getString(R.string.media_transfer_headphone_name)
+                                : context.getString(R.string.media_transfer_wired_headphone_name);
+                break;
             case TYPE_USB_DEVICE:
             case TYPE_USB_HEADSET:
             case TYPE_USB_ACCESSORY:
-                name = context.getString(R.string.media_transfer_wired_usb_device_name);
+                name =
+                        inputRoutingEnabledAndIsDesktop()
+                                ? context.getString(R.string.media_transfer_usb_speaker_name)
+                                : context.getString(R.string.media_transfer_wired_headphone_name);
                 break;
             case TYPE_DOCK:
                 name = context.getString(R.string.media_transfer_dock_speaker_device_name);
@@ -139,6 +149,16 @@
                 .contains("tablet");
     }
 
+    static boolean isDesktop() {
+        return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+                .contains("desktop");
+    }
+
+    static boolean inputRoutingEnabledAndIsDesktop() {
+        return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+                && isDesktop();
+    }
+
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
     @SuppressWarnings("NewApi")
     @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index e2d58d6..da5f428 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -47,6 +47,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowSystemProperties;
 
 @RunWith(RobolectricTestRunner.class)
 public class PhoneMediaDeviceTest {
@@ -105,12 +106,37 @@
         when(mInfo.getName()).thenReturn(deviceName);
 
         assertThat(mPhoneMediaDevice.getName())
-                .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
+                .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name));
 
         when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
 
         assertThat(mPhoneMediaDevice.getName())
-                .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
+                .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name));
+
+        when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+
+        assertThat(mPhoneMediaDevice.getName()).isEqualTo(getMediaTransferThisDeviceName(mContext));
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+    @Test
+    public void getName_returnCorrectName_desktop() {
+        ShadowSystemProperties.override("ro.build.characteristics", "desktop");
+
+        when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
+
+        assertThat(mPhoneMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+        when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET);
+
+        assertThat(mPhoneMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+        when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
+
+        assertThat(mPhoneMediaDevice.getName())
+                .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name));
 
         when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index afbe84c..fbce6ca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -261,22 +261,13 @@
 
       public static HashMap<String, String> getAllFlags(IContentProvider provider) {
         HashMap<String, String> allFlags = new HashMap<String, String>();
-        try {
-            Bundle args = new Bundle();
-            args.putInt(Settings.CALL_METHOD_USER_KEY,
-                ActivityManager.getService().getCurrentUser().id);
-            Bundle b = provider.call(new AttributionSource(Process.myUid(),
-                    resolveCallingPackage(), null), Settings.AUTHORITY,
-                    Settings.CALL_METHOD_LIST_CONFIG, null, args);
-            if (b != null) {
-                Map<String, String> flagsToValues =
-                    (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
-                allFlags.putAll(flagsToValues);
+        for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
+            List<String> keys = new ArrayList<>(properties.getKeyset());
+            for (String flagName : properties.getKeyset()) {
+                String fullName = properties.getNamespace() + "/" + flagName;
+                allFlags.put(fullName, properties.getString(flagName, null));
             }
-        } catch (RemoteException e) {
-            throw new RuntimeException("Failed in IPC", e);
         }
-
         return allFlags;
       }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ba59ce8..0ae4da5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -545,6 +545,10 @@
                 reportDeviceConfigAccess(prefix);
                 return result;
             }
+            case Settings.CALL_METHOD_LIST_NAMESPACES_CONFIG -> {
+                Bundle result = packageNamespacesForCallResult(getAllConfigFlagNamespaces());
+                return result;
+            }
             case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> {
                 RemoteCallback callback = args.getParcelable(
                         Settings.CALL_METHOD_MONITOR_CALLBACK_KEY);
@@ -1337,6 +1341,23 @@
     }
 
     @NonNull
+    private HashSet<String> getAllConfigFlagNamespaces() {
+        Set<String> flagNames = getAllConfigFlags(null).keySet();
+        HashSet<String> namespaces = new HashSet();
+        for (String name : flagNames) {
+            int slashIndex = name.indexOf("/");
+            boolean validSlashIndex = slashIndex != -1
+                        && slashIndex != 0
+                        && slashIndex != name.length();
+            if (validSlashIndex) {
+                String namespace = name.substring(0, slashIndex);
+                namespaces.add(namespace);
+            }
+        }
+        return namespaces;
+    }
+
+    @NonNull
     private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2561,6 +2582,12 @@
         return result;
     }
 
+    private Bundle packageNamespacesForCallResult(@NonNull HashSet<String> namespaces) {
+        Bundle result = new Bundle();
+        result.putSerializable(Settings.NameValueTable.VALUE, namespaces);
+        return result;
+    }
+
     private void setMonitorCallback(RemoteCallback callback) {
         if (callback == null) {
             return;
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 207c961..855ebe3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -538,6 +538,13 @@
 }
 
 flag {
+  name: "status_bar_connected_displays"
+  namespace: "systemui"
+  description: "Shows the status bar on connected displays"
+  bug: "362720336"
+}
+
+flag {
     name: "status_bar_switch_to_spn_from_data_spn"
     namespace: "systemui"
     description: "Fix usage of the SPN broadcast extras"
@@ -548,10 +555,10 @@
 }
 
 flag {
-    name: "haptic_volume_slider"
+    name: "status_bar_simple_fragment"
     namespace: "systemui"
-    description: "Adds haptic feedback to the volume slider."
-    bug: "316953430"
+    description: "Feature flag for refactoring the collapsed status bar fragment"
+    bug: "364360986"
 }
 
 flag {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
index c01396a..4674d6e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
@@ -16,16 +16,15 @@
 
 package com.android.compose.windowsizeclass
 
-import android.view.WindowManager
 import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
 import androidx.compose.material3.windowsizeclass.WindowSizeClass
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.graphics.toComposeRect
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
+import androidx.window.layout.WindowMetricsCalculator
 
 val LocalWindowSizeClass =
     staticCompositionLocalOf<WindowSizeClass> {
@@ -42,10 +41,7 @@
     LocalConfiguration.current
     val density = LocalDensity.current
     val context = LocalContext.current
-    val metrics =
-        remember(context) {
-            context.getSystemService(WindowManager::class.java)!!.currentWindowMetrics
-        }
+    val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
     val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
     return WindowSizeClass.calculateFromSize(size)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 163b355..8321238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.bouncer.ui.composable
 
-import android.view.HapticFeedbackConstants
 import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
@@ -133,10 +132,7 @@
         // Perform haptic feedback, but only if the current dot is not null, so we don't perform it
         // when the UI first shows up or when the user lifts their pointer/finger.
         if (currentDot != null) {
-            view.performHapticFeedback(
-                HapticFeedbackConstants.VIRTUAL_KEY,
-                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
-            )
+            viewModel.performDotFeedback(view)
         }
 
         if (!isAnimationEnabled) {
@@ -206,10 +202,7 @@
     // Show the failure animation if the user entered the wrong input.
     LaunchedEffect(animateFailure) {
         if (animateFailure) {
-            showFailureAnimation(
-                dots = dots,
-                scalingAnimatables = dotScalingAnimatables,
-            )
+            showFailureAnimation(dots = dots, scalingAnimatables = dotScalingAnimatables)
             viewModel.onFailureAnimationShown()
         }
     }
@@ -358,15 +351,10 @@
                     (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
                 drawCircle(
                     center =
-                        pixelOffset(
-                            dot,
-                            spacing,
-                            horizontalOffset,
-                            verticalOffset + appearOffset,
-                        ),
+                        pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
                     color =
                         dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
-                    radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value
+                    radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
                 )
             }
         }
@@ -387,7 +375,7 @@
                             delayMillis = 33 * dot.y,
                             durationMillis = 450,
                             easing = Easings.LegacyDecelerate,
-                        )
+                        ),
                 )
             }
         }
@@ -400,7 +388,7 @@
                             delayMillis = 0,
                             durationMillis = 450 + (33 * dot.y),
                             easing = Easings.StandardDecelerate,
-                        )
+                        ),
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 489e24e..0830c9b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.bouncer.ui.composable
 
-import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
+import android.view.View
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
@@ -72,11 +72,7 @@
 
 /** Renders the PIN button pad. */
 @Composable
-fun PinPad(
-    viewModel: PinBouncerViewModel,
-    verticalSpacing: Dp,
-    modifier: Modifier = Modifier,
-) {
+fun PinPad(viewModel: PinBouncerViewModel, verticalSpacing: Dp, modifier: Modifier = Modifier) {
     DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
 
     val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
@@ -104,7 +100,7 @@
         columns = columns,
         verticalSpacing = verticalSpacing,
         horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
-        modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid")
+        modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid"),
     ) {
         repeat(9) { index ->
             DigitButton(
@@ -126,10 +122,11 @@
                 ),
             isInputEnabled = isInputEnabled,
             onClicked = viewModel::onBackspaceButtonClicked,
+            onPointerDown = viewModel::onBackspaceButtonPressed,
             onLongPressed = viewModel::onBackspaceButtonLongPressed,
             appearance = backspaceButtonAppearance,
             scaling = buttonScaleAnimatables[9]::value,
-            elementId = "delete_button"
+            elementId = "delete_button",
         )
 
         DigitButton(
@@ -138,7 +135,7 @@
             onClicked = viewModel::onPinButtonClicked,
             scaling = buttonScaleAnimatables[10]::value,
             isAnimationEnabled = isDigitButtonAnimationEnabled,
-            onPointerDown = viewModel::onDigitButtonDown
+            onPointerDown = viewModel::onDigitButtonDown,
         )
 
         ActionButton(
@@ -152,7 +149,7 @@
             onClicked = viewModel::onAuthenticateButtonClicked,
             appearance = confirmButtonAppearance,
             scaling = buttonScaleAnimatables[11]::value,
-            elementId = "key_enter"
+            elementId = "key_enter",
         )
     }
 }
@@ -162,7 +159,7 @@
     digit: Int,
     isInputEnabled: Boolean,
     onClicked: (Int) -> Unit,
-    onPointerDown: () -> Unit,
+    onPointerDown: (View?) -> Unit,
     scaling: () -> Float,
     isAnimationEnabled: Boolean,
 ) {
@@ -178,7 +175,7 @@
                 val scale = if (isAnimationEnabled) scaling() else 1f
                 scaleX = scale
                 scaleY = scale
-            }
+            },
     ) { contentColor ->
         // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes
         // it into Text, use that here, to animate more efficiently.
@@ -197,6 +194,7 @@
     onClicked: () -> Unit,
     elementId: String,
     onLongPressed: (() -> Unit)? = null,
+    onPointerDown: ((View?) -> Unit)? = null,
     appearance: ActionButtonAppearance,
     scaling: () -> Float,
 ) {
@@ -222,18 +220,16 @@
         foregroundColor = foregroundColor,
         isAnimationEnabled = true,
         elementId = elementId,
+        onPointerDown = onPointerDown,
         modifier =
             Modifier.graphicsLayer {
                 alpha = hiddenAlpha
                 val scale = scaling()
                 scaleX = scale
                 scaleY = scale
-            }
+            },
     ) { contentColor ->
-        Icon(
-            icon = icon,
-            tint = contentColor(),
-        )
+        Icon(icon = icon, tint = contentColor())
     }
 }
 
@@ -247,22 +243,13 @@
     modifier: Modifier = Modifier,
     elementId: String? = null,
     onLongPressed: (() -> Unit)? = null,
-    onPointerDown: (() -> Unit)? = null,
+    onPointerDown: ((View?) -> Unit)? = null,
     content: @Composable (contentColor: () -> Color) -> Unit,
 ) {
     val interactionSource = remember { MutableInteractionSource() }
     val isPressed by interactionSource.collectIsPressedAsState()
     val indication = LocalIndication.current.takeUnless { isPressed }
-
     val view = LocalView.current
-    LaunchedEffect(isPressed) {
-        if (isPressed) {
-            view.performHapticFeedback(
-                HapticFeedbackConstants.VIRTUAL_KEY,
-                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
-            )
-        }
-    }
 
     // Pin button animation specification is asymmetric: fast animation to the pressed state, and a
     // slow animation upon release. Note that isPressed is guaranteed to be true for at least the
@@ -277,7 +264,7 @@
         animateDpAsState(
             if (isAnimationEnabled && isPressed) 24.dp else pinButtonMaxSize / 2,
             label = "PinButton round corners",
-            animationSpec = tween(animDurationMillis, easing = animEasing)
+            animationSpec = tween(animDurationMillis, easing = animEasing),
         )
     val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing)
     val containerColor: Color by
@@ -287,7 +274,7 @@
                 else -> backgroundColor
             },
             label = "Pin button container color",
-            animationSpec = colorAnimationSpec
+            animationSpec = colorAnimationSpec,
         )
     val contentColor =
         animateColorAsState(
@@ -296,7 +283,7 @@
                 else -> foregroundColor
             },
             label = "Pin button container color",
-            animationSpec = colorAnimationSpec
+            animationSpec = colorAnimationSpec,
         )
 
     Box(
@@ -319,11 +306,11 @@
                             interactionSource = interactionSource,
                             indication = indication,
                             onClick = onClicked,
-                            onLongClick = onLongPressed
+                            onLongClick = onLongPressed,
                         )
                         .pointerInteropFilter { motionEvent ->
                             if (motionEvent.action == MotionEvent.ACTION_DOWN) {
-                                onPointerDown?.let { it() }
+                                onPointerDown?.let { it(view) }
                             }
                             false
                         }
@@ -353,10 +340,7 @@
                 animatable.animateTo(
                     targetValue = 1f,
                     animationSpec =
-                        tween(
-                            durationMillis = pinButtonErrorRevertMs,
-                            easing = Easings.Legacy,
-                        ),
+                        tween(durationMillis = pinButtonErrorRevertMs, easing = Easings.Legacy),
                 )
             }
         }
@@ -364,9 +348,7 @@
 }
 
 /** Returns the amount of horizontal spacing between columns, in dips. */
-private fun calculateHorizontalSpacingBetweenColumns(
-    gridWidth: Dp,
-): Dp {
+private fun calculateHorizontalSpacingBetweenColumns(gridWidth: Dp): Dp {
     return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 296fc27..dcf32b2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -16,15 +16,10 @@
 
 package com.android.systemui.common.ui.compose.windowinsets
 
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.systemBars
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -36,9 +31,6 @@
 /** The corner radius in px of the current display. */
 val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
 
-/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */
-val LocalRawScreenHeight = staticCompositionLocalOf { 0f }
-
 @Composable
 fun ScreenDecorProvider(
     displayCutout: StateFlow<DisplayCutout>,
@@ -48,22 +40,9 @@
     val cutout by displayCutout.collectAsStateWithLifecycle()
     val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
 
-    val density = LocalDensity.current
-    val navBarHeight =
-        with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
-    val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
-    val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
-    val screenHeight =
-        with(density) {
-            (LocalConfiguration.current.screenHeightDp.dp +
-                    maxOf(statusBarHeight, displayCutoutHeight))
-                .toPx()
-        } + navBarHeight
-
     CompositionLocalProvider(
         LocalScreenCornerRadius provides screenCornerRadiusDp,
         LocalDisplayCutout provides cutout,
-        LocalRawScreenHeight provides screenHeight,
     ) {
         content()
     }
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 5f7b1ad..f4d9e82 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
@@ -35,7 +35,6 @@
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.res.R
 import com.android.systemui.util.animation.MeasurementInput
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 object MediaCarousel {
     object Elements {
@@ -47,7 +46,6 @@
     }
 }
 
-@ExperimentalCoroutinesApi
 @Composable
 fun SceneScope.MediaCarousel(
     isVisible: Boolean,
@@ -79,7 +77,7 @@
                                         offsetProvider?.invoke() ?: IntOffset.Zero
                                     )
                                 }
-                            }
+                            },
                         )
                         .layout { measurable, constraints ->
                             val placeable = measurable.measure(constraints)
@@ -89,7 +87,7 @@
                                 MeasurementInput(placeable.width, placeable.height)
                             carouselController.setSceneContainerSize(
                                 placeable.width,
-                                placeable.height
+                                placeable.height,
                             )
 
                             layout(placeable.width, placeable.height) {
@@ -106,7 +104,7 @@
                     }
                 },
                 update = { it.setView(carouselController.mediaFrame) },
-                onRelease = { it.removeAllViews() }
+                onRelease = { it.removeAllViews() },
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index 897a861..a2ae8bb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -24,9 +24,11 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import kotlin.math.max
 import kotlin.math.roundToInt
 import kotlin.math.tanh
@@ -36,9 +38,10 @@
 @Composable
 fun Modifier.stackVerticalOverscroll(
     coroutineScope: CoroutineScope,
-    canScrollForward: () -> Boolean
+    canScrollForward: () -> Boolean,
 ): Modifier {
-    val screenHeight = LocalRawScreenHeight.current
+    val screenHeight =
+        with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
     val overscrollOffset = remember { Animatable(0f) }
     val stackNestedScrollConnection = remember {
         NotificationStackNestedScrollConnection(
@@ -60,10 +63,10 @@
                     overscrollOffset.animateTo(
                         targetValue = 0f,
                         initialVelocity = velocityAvailable,
-                        animationSpec = tween()
+                        animationSpec = tween(),
                     )
                 }
-            }
+            },
         )
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 91ecfc1..1b99a96 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,6 +19,7 @@
 
 import android.util.Log
 import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.background
@@ -29,6 +30,8 @@
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.absoluteOffset
@@ -36,9 +39,11 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imeAnimationTarget
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.windowInsetsBottomHeight
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
@@ -68,6 +73,7 @@
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
@@ -81,7 +87,6 @@
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
 import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -96,6 +101,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
 object Notifications {
@@ -171,7 +177,7 @@
             setCurrent = { scrollOffset = it },
             min = minScrollOffset,
             max = maxScrollOffset,
-            delta
+            delta,
         )
     }
 
@@ -209,8 +215,8 @@
                             calculateHeadsUpPlaceholderYOffset(
                                 scrollOffset.roundToInt(),
                                 minScrollOffset.roundToInt(),
-                                stackScrollView.topHeadsUpHeight
-                            )
+                                stackScrollView.topHeadsUpHeight,
+                            ),
                     )
                 }
                 .thenIf(isHeadsUp) {
@@ -218,11 +224,8 @@
                             bottomBehavior = NestedScrollBehavior.EdgeAlways
                         )
                         .nestedScroll(nestedScrollConnection)
-                        .scrollable(
-                            orientation = Orientation.Vertical,
-                            state = scrollableState,
-                        )
-                }
+                        .scrollable(orientation = Orientation.Vertical, state = scrollableState)
+                },
     )
 }
 
@@ -259,6 +262,7 @@
  * Adds the space where notification stack should appear in the scene, with a scrim and nested
  * scrolling.
  */
+@OptIn(ExperimentalLayoutApi::class)
 @Composable
 fun SceneScope.NotificationScrollingStack(
     shadeSession: SaveableSession,
@@ -291,7 +295,7 @@
     val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
     val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp
 
-    val screenHeight = LocalRawScreenHeight.current
+    val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
 
     /**
      * The height in px of the contents of notification stack. Depending on the number of
@@ -325,6 +329,14 @@
         screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() }
     }
 
+    val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false)
+
+    // The bottom Y bound of the currently focused remote input notification.
+    val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f)
+
+    // The top y bound of the IME.
+    val imeTop = remember { mutableFloatStateOf(0f) }
+
     // we are not scrolled to the top unless the scrim is at its maximum offset.
     LaunchedEffect(viewModel, scrimOffset) {
         snapshotFlow { scrimOffset.value >= 0f }
@@ -342,15 +354,34 @@
     LaunchedEffect(syntheticScroll, scrimOffset, scrollState) {
         snapshotFlow { syntheticScroll.value }
             .collect { delta ->
-                val minOffset = minScrimOffset()
-                if (scrimOffset.value > minOffset) {
-                    val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
-                    scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset))
-                    if (remainingDelta > 0f) {
-                        scrollState.scrollBy(remainingDelta)
-                    }
-                } else {
-                    scrollState.scrollTo(delta.roundToInt())
+                scrollNotificationStack(
+                    scope = coroutineScope,
+                    delta = delta,
+                    animate = false,
+                    scrimOffset = scrimOffset,
+                    minScrimOffset = minScrimOffset,
+                    scrollState = scrollState,
+                )
+            }
+    }
+
+    // if remote input state changes, compare the row and IME's overlap and offset the scrim and
+    // placeholder accordingly.
+    LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) {
+        imeTop.floatValue = 0f
+        snapshotFlow { imeTop.floatValue }
+            .collect { imeTopValue ->
+                // only scroll the stack if ime value has been populated (ime placeholder has been
+                // composed at least once), and our remote input row overlaps with the ime bounds.
+                if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) {
+                    scrollNotificationStack(
+                        scope = coroutineScope,
+                        delta = remoteInputRowBottom - imeTopValue,
+                        animate = true,
+                        scrimOffset = scrimOffset,
+                        minScrimOffset = minScrimOffset,
+                        scrollState = scrollState,
+                    )
                 }
             }
     }
@@ -394,12 +425,12 @@
                         scrimOffset.value < 0 &&
                             layoutState.isTransitioning(
                                 from = Scenes.Shade,
-                                to = Scenes.QuickSettings
+                                to = Scenes.QuickSettings,
                             )
                     ) {
                         IntOffset(
                             x = 0,
-                            y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+                            y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(),
                         )
                     } else {
                         IntOffset(x = 0, y = scrimOffset.value.roundToInt())
@@ -458,13 +489,11 @@
                     .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
                     .debugBackground(viewModel, DEBUG_BOX_COLOR)
         ) {
-            NotificationPlaceholder(
-                stackScrollView = stackScrollView,
-                viewModel = viewModel,
+            Column(
                 modifier =
                     Modifier.verticalNestedScrollToScene(
                             topBehavior = NestedScrollBehavior.EdgeWithPreview,
-                            isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }
+                            isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
                         )
                         .thenIf(shadeMode == ShadeMode.Single) {
                             Modifier.nestedScroll(scrimNestedScrollConnection)
@@ -473,18 +502,31 @@
                         .verticalScroll(scrollState)
                         .padding(top = topPadding)
                         .fillMaxWidth()
-                        .notificationStackHeight(
-                            view = stackScrollView,
-                            totalVerticalPadding = topPadding + bottomPadding,
-                        )
-                        .onSizeChanged { size -> stackHeight.intValue = size.height },
-            )
+            ) {
+                NotificationPlaceholder(
+                    stackScrollView = stackScrollView,
+                    viewModel = viewModel,
+                    modifier =
+                        Modifier.notificationStackHeight(
+                                view = stackScrollView,
+                                totalVerticalPadding = topPadding + bottomPadding,
+                            )
+                            .onSizeChanged { size -> stackHeight.intValue = size.height },
+                )
+                Spacer(
+                    modifier =
+                        Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget)
+                            .onGloballyPositioned { coordinates: LayoutCoordinates ->
+                                imeTop.floatValue = screenHeight - coordinates.size.height
+                            }
+                )
+            }
         }
         if (shouldIncludeHeadsUpSpace) {
             HeadsUpNotificationSpace(
                 stackScrollView = stackScrollView,
                 viewModel = viewModel,
-                modifier = Modifier.padding(top = topPadding)
+                modifier = Modifier.padding(top = topPadding),
             )
         }
     }
@@ -572,6 +614,42 @@
     )
 }
 
+private suspend fun scrollNotificationStack(
+    scope: CoroutineScope,
+    delta: Float,
+    animate: Boolean,
+    scrimOffset: Animatable<Float, AnimationVector1D>,
+    minScrimOffset: () -> Float,
+    scrollState: ScrollState,
+) {
+    val minOffset = minScrimOffset()
+    if (scrimOffset.value > minOffset) {
+        val remainingDelta =
+            (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt()
+        if (remainingDelta > 0) {
+            if (animate) {
+                // launch a new coroutine for the remainder animation so that it doesn't suspend the
+                // scrim animation, allowing both to play simultaneously.
+                scope.launch { scrollState.animateScrollTo(remainingDelta) }
+            } else {
+                scrollState.scrollTo(remainingDelta)
+            }
+        }
+        val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+        if (animate) {
+            scrimOffset.animateTo(newScrimOffset)
+        } else {
+            scrimOffset.snapTo(newScrimOffset)
+        }
+    } else {
+        if (animate) {
+            scrollState.animateScrollBy(delta)
+        } else {
+            scrollState.scrollBy(delta)
+        }
+    }
+}
+
 private fun calculateCornerRadius(
     scrimCornerRadius: Dp,
     screenCornerRadius: Dp,
@@ -618,7 +696,7 @@
     setCurrent: (Float) -> Unit,
     min: Float,
     max: Float,
-    delta: Float
+    delta: Float,
 ): Float {
     return if (delta < 0 && current > min) {
         val remainder = (current + delta - min).coerceAtMost(0f)
@@ -631,10 +709,7 @@
     } else 0f
 }
 
-private inline fun debugLog(
-    viewModel: NotificationsPlaceholderViewModel,
-    msg: () -> Any,
-) {
+private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) {
     if (viewModel.isDebugLoggingEnabled) {
         Log.d(TAG, msg().toString())
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index fa92bef34..d91958a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -61,6 +61,7 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
@@ -79,7 +80,6 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -229,17 +229,16 @@
                 }
                 .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
     ) {
+        val density = LocalDensity.current
         val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
         val isCustomizerShowing by
             viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
         val customizingAnimationDuration by
             viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
-        val screenHeight = LocalRawScreenHeight.current
+        val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
 
         BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() }
 
-        val collapsedHeaderHeight =
-            with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
         val lifecycleOwner = LocalLifecycleOwner.current
         val footerActionsViewModel =
             remember(lifecycleOwner, viewModel) {
@@ -268,7 +267,6 @@
 
         val navBarBottomHeight =
             WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
-        val density = LocalDensity.current
         val bottomPadding by
             animateDpAsState(
                 targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
@@ -394,7 +392,7 @@
                             { mediaOffset.roundToPx() },
                         )
                     }
-                    Box(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) {
+                    Column(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) {
                         if (mediaInRow) {
                             Layout(content = content, measurePolicy = landscapeQsMediaMeasurePolicy)
                         } else {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index deef652..9552564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -16,18 +16,26 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -39,11 +47,15 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
+    private val msdlPlayer = kosmos.fakeMSDLPlayer
+    private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
+    private val authInteractionProperties = AuthInteractionProperties()
     private val underTest =
         kosmos.pinBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true),
             onIntentionalUserInput = {},
             authenticationMethod = AuthenticationMethodModel.Pin,
+            bouncerHapticPlayer = bouncerHapticPlayer,
         )
 
     @Before
@@ -77,4 +89,42 @@
             underTest.onAuthenticateButtonClicked()
             assertThat(animateFailure).isFalse()
         }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun onAuthenticationResult_playUnlockTokenIfSuccessful() =
+        testScope.runTest {
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            // Correct PIN:
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+            underTest.onAuthenticateButtonClicked()
+            runCurrent()
+
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+        }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun onAuthenticationResult_playFailureTokenIfFailure() =
+        testScope.runTest {
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            // Wrong PIN:
+            FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+            underTest.onAuthenticateButtonClicked()
+            runCurrent()
+
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 7c773a9..c163c6f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.authenticationRepository
@@ -27,12 +29,16 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,10 +61,13 @@
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+    private val msdlPlayer: FakeMSDLPlayer = kosmos.fakeMSDLPlayer
+    private val bouncerHapticHelper = kosmos.bouncerHapticPlayer
     private val underTest =
         kosmos.patternBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
             onIntentionalUserInput = {},
+            bouncerHapticPlayer = bouncerHapticHelper,
         )
 
     private val containerSize = 90 // px
@@ -115,10 +124,7 @@
                     .that(selectedDots)
                     .isEqualTo(
                         CORRECT_PATTERN.subList(0, index + 1).map {
-                            PatternDotViewModel(
-                                x = it.x,
-                                y = it.y,
-                            )
+                            PatternDotViewModel(x = it.x, y = it.y)
                         }
                     )
                 assertWithMessage("Wrong current dot for index $index")
@@ -174,7 +180,7 @@
                     listOf(
                         PatternDotViewModel(0, 0),
                         PatternDotViewModel(1, 0),
-                        PatternDotViewModel(2, 0)
+                        PatternDotViewModel(2, 0),
                     )
                 )
         }
@@ -200,7 +206,7 @@
                     listOf(
                         PatternDotViewModel(1, 0),
                         PatternDotViewModel(1, 1),
-                        PatternDotViewModel(1, 2)
+                        PatternDotViewModel(1, 2),
                     )
                 )
         }
@@ -228,7 +234,7 @@
                     listOf(
                         PatternDotViewModel(2, 0),
                         PatternDotViewModel(1, 1),
-                        PatternDotViewModel(0, 2)
+                        PatternDotViewModel(0, 2),
                     )
                 )
         }
@@ -300,10 +306,7 @@
             val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
             repeat(attempts) { attempt ->
                 underTest.onDragStart()
-                CORRECT_PATTERN.subList(
-                        0,
-                        kosmos.authenticationRepository.minPatternLength - 1,
-                    )
+                CORRECT_PATTERN.subList(0, kosmos.authenticationRepository.minPatternLength - 1)
                     .forEach { coordinate ->
                         underTest.onDrag(
                             xPx = 30f * coordinate.x + 15,
@@ -341,6 +344,16 @@
             assertThat(authResult).isTrue()
         }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun performDotFeedback_deliversDragToken() =
+        testScope.runTest {
+            underTest.performDotFeedback(null)
+
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
     private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
         underTest.onDragStart()
         coordinatesDragged.forEach(::dragToCoordinate)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2ee4aee..af5f2ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -35,12 +36,15 @@
 import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
 import com.android.systemui.classifier.fakeFalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
 import com.google.common.truth.Truth.assertThat
 import kotlin.random.Random
 import kotlin.random.nextInt
@@ -64,11 +68,14 @@
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private val msdlPlayer = kosmos.fakeMSDLPlayer
+    private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
     private val underTest by lazy {
         kosmos.pinBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true),
             onIntentionalUserInput = {},
             authenticationMethod = AuthenticationMethodModel.Pin,
+            bouncerHapticPlayer = bouncerHapticPlayer,
         )
     }
 
@@ -97,6 +104,7 @@
                     isInputEnabled = MutableStateFlow(true),
                     onIntentionalUserInput = {},
                     authenticationMethod = AuthenticationMethodModel.Sim,
+                    bouncerHapticPlayer = bouncerHapticPlayer,
                 )
 
             assertThat(underTest.isSimAreaVisible).isTrue()
@@ -122,6 +130,7 @@
                     isInputEnabled = MutableStateFlow(true),
                     onIntentionalUserInput = {},
                     authenticationMethod = AuthenticationMethodModel.Pin,
+                    bouncerHapticPlayer = bouncerHapticPlayer,
                 )
             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -487,11 +496,39 @@
         testScope.runTest {
             lockDeviceAndOpenPinBouncer()
 
-            underTest.onDigitButtonDown()
+            underTest.onDigitButtonDown(null)
 
             assertTrue(kosmos.fakeFalsingCollector.wasLastGestureAvoided())
         }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun onDigiButtonDown_deliversKeyStandardToken() =
+        testScope.runTest {
+            underTest.onDigitButtonDown(null)
+
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_STANDARD)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun onBackspaceButtonPressed_deliversKeyDeleteToken() {
+        underTest.onBackspaceButtonPressed(null)
+
+        assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_DELETE)
+        assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun onBackspaceButtonLongPressed_deliversLongPressToken() {
+        underTest.onBackspaceButtonLongPressed()
+
+        assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS)
+        assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+    }
+
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 956c129..2028d288 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -25,6 +25,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityManager;
 
@@ -33,6 +35,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -201,6 +204,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
     public void testTrackpadGesture() {
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
         when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
@@ -208,6 +212,14 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+    public void testTrackpadGesture_touchScreenSource_false() {
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+    }
+
+    @Test
     public void testAddAndRemoveFalsingBeliefListener() {
         verify(mHistoryTracker, never()).addBeliefListener(any());
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index df4b048..5a4799c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -312,6 +312,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
     public void test_IsFromTrackpad() {
         MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 70529cc..ee65fbd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -21,6 +21,8 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
@@ -28,6 +30,7 @@
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.EditModeState
 import com.android.systemui.coroutines.collectLastValue
@@ -35,7 +38,6 @@
 import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -93,6 +95,7 @@
                         bgScope = applicationCoroutineScope,
                         mainDispatcher = testDispatcher,
                         centralSurfacesOpt = centralSurfacesOptional,
+                        uiEventLogger = uiEventLoggerFake,
                     )
                     .apply { start() }
 
@@ -119,7 +122,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.PRIMARY_BOUNCER,
                     to = KeyguardState.GONE,
-                    testScope = this
+                    testScope = this,
                 )
 
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -140,7 +143,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.PRIMARY_BOUNCER,
                     to = KeyguardState.GONE,
-                    testScope = this
+                    testScope = this,
                 )
 
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -161,7 +164,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.PRIMARY_BOUNCER,
                     to = KeyguardState.GONE,
-                    testScope = this
+                    testScope = this,
                 )
 
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
@@ -181,7 +184,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.ALTERNATE_BOUNCER,
                     to = KeyguardState.GONE,
-                    testScope = this
+                    testScope = this,
                 )
                 // Scene change will be handled in EditWidgetsActivity not here
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -200,7 +203,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GONE,
                     to = KeyguardState.LOCKSCREEN,
-                    testScope = this
+                    testScope = this,
                 )
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
             }
@@ -220,7 +223,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.OCCLUDED,
-                    testScope = this
+                    testScope = this,
                 )
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
             }
@@ -240,7 +243,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.OCCLUDED,
-                    testScope = this
+                    testScope = this,
                 )
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
             }
@@ -258,7 +261,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.LOCKSCREEN,
-                    testScope = this
+                    testScope = this,
                 )
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
             }
@@ -276,7 +279,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.OFF,
-                    testScope = this
+                    testScope = this,
                 )
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
@@ -298,7 +301,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.OFF,
-                    testScope = this
+                    testScope = this,
                 )
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2)
@@ -307,7 +310,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.OFF,
                     to = KeyguardState.GLANCEABLE_HUB,
-                    testScope = this
+                    testScope = this,
                 )
 
                 advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
@@ -327,7 +330,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.LOCKSCREEN,
-                    testScope = this
+                    testScope = this,
                 )
                 updateDocked(true)
 
@@ -349,7 +352,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.GLANCEABLE_HUB,
                     to = KeyguardState.LOCKSCREEN,
-                    testScope = this
+                    testScope = this,
                 )
                 updateDocked(true)
 
@@ -361,7 +364,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.LOCKSCREEN,
                     to = KeyguardState.DREAMING,
-                    testScope = this
+                    testScope = this,
                 )
                 advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
@@ -511,6 +514,9 @@
 
                 advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
+                assertThat(uiEventLoggerFake.logs.first().eventId)
+                    .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
+                assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
             }
         }
 
@@ -526,7 +532,7 @@
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
                     from = KeyguardState.DOZING,
                     to = KeyguardState.GLANCEABLE_HUB,
-                    testScope = this
+                    testScope = this,
                 )
 
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index c2acc5f..160865d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -31,8 +31,11 @@
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -211,7 +214,7 @@
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
             kosmos.fakeUserRepository.setSelectedUserInfo(
                 primaryUser,
-                SelectionStatus.SELECTION_COMPLETE
+                SelectionStatus.SELECTION_COMPLETE,
             )
 
             kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
@@ -240,6 +243,49 @@
         }
 
     @Test
+    fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+        testScope.runTest {
+            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = this,
+            )
+            kosmos.powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+        }
+
+    @Test
+    fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
+        testScope.runTest {
+            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+            assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
+                .isEqualTo(KeyguardState.LOCKSCREEN)
+
+            kosmos.powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+        }
+
+    @Test
     fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
         testScope.runTest {
             kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
@@ -273,7 +319,7 @@
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                     DeviceEntryRestrictionReason.UserLockdown,
                 LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    DeviceEntryRestrictionReason.PolicyLockdown
+                    DeviceEntryRestrictionReason.PolicyLockdown,
             )
         }
 
@@ -285,7 +331,7 @@
             kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
             kosmos.fakeSystemPropertiesHelper.set(
                 DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
-                "not mainline reboot"
+                "not mainline reboot",
             )
             runCurrent()
 
@@ -321,7 +367,7 @@
             kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
             kosmos.fakeSystemPropertiesHelper.set(
                 DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
-                "not mainline reboot"
+                "not mainline reboot",
             )
             runCurrent()
 
@@ -358,7 +404,7 @@
             kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
             kosmos.fakeSystemPropertiesHelper.set(
                 DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
-                "not mainline reboot"
+                "not mainline reboot",
             )
             runCurrent()
 
@@ -394,12 +440,12 @@
                 collectLastValue(underTest.deviceEntryRestrictionReason)
             kosmos.fakeSystemPropertiesHelper.set(
                 DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
-                DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+                DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE,
             )
             kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
                 AuthenticationFlags(
                     userId = 1,
-                    flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+                    flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT,
                 )
             )
             runCurrent()
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 686b518..366b55d 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
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.classifier.falsingManager
 import com.android.systemui.haptics.fakeVibratorHelper
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.core.FakeLogBuffer
@@ -68,11 +69,13 @@
         vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration
 
         whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true)
+        kosmos.falsingManager.setFalseLongTap(false)
 
         longPressEffect =
             QSLongPressEffect(
                 vibratorHelper,
                 kosmos.keyguardStateController,
+                kosmos.falsingManager,
                 FakeLogBuffer.Factory.create(),
             )
         longPressEffect.callback = callback
@@ -180,11 +183,7 @@
 
         // THEN the expected texture is played
         val reverseHaptics =
-            LongPressHapticBuilder.createReversedEffect(
-                progress,
-                lowTickDuration,
-                effectDuration,
-            )
+            LongPressHapticBuilder.createReversedEffect(progress, lowTickDuration, effectDuration)
         assertThat(reverseHaptics).isNotNull()
         assertThat(vibratorHelper.hasVibratedWithEffects(reverseHaptics!!)).isTrue()
     }
@@ -224,6 +223,20 @@
         }
 
     @Test
+    fun onAnimationComplete_isFalseLongClick_effectEndsInIdleWithReset() =
+        testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
+            // GIVEN that the long-click is false
+            kosmos.falsingManager.setFalseLongTap(true)
+
+            // GIVEN that the animation completes
+            longPressEffect.handleAnimationComplete()
+
+            // THEN the long-press effect ends in the idle state and the properties are reset
+            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+            verify(callback, times(1)).onResetProperties()
+        }
+
+    @Test
     fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
         testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
             // GIVEN that the animation completes
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 0c716137..639737b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
 import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
@@ -53,6 +54,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -81,6 +83,7 @@
                 Optional.of(kosmos.touchpadGesturesInteractor),
                 KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
                 hasTouchpadTutorialScreens,
+                mock<InputDeviceTutorialLogger>(),
                 SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
             )
         lifecycle.addObserver(viewModel)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 6c3c7ef..fcf4662 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -16,7 +16,10 @@
  */
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.app.Flags
 import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
 import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -25,6 +28,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
@@ -35,7 +39,11 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
 import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
@@ -43,6 +51,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
+import java.time.Duration
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -66,8 +75,13 @@
     private val kosmos = testKosmos()
     private val testDispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
+
     private val settings = kosmos.fakeSettings
 
+    private val zenModeRepository = kosmos.fakeZenModeRepository
+    private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
+    private val secureSettingsRepository = kosmos.secureSettingsRepository
+
     @Mock private lateinit var zenModeController: ZenModeController
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var conditionUri: Uri
@@ -85,17 +99,36 @@
             DoNotDisturbQuickAffordanceConfig(
                 context,
                 zenModeController,
+                kosmos.zenModeInteractor,
                 settings,
                 userTracker,
                 testDispatcher,
+                testScope.backgroundScope,
                 conditionUri,
                 enableZenModeDialog,
             )
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
     fun dndNotAvailable_pickerStateHidden() =
         testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(false)
+            runCurrent()
+
+            val result = underTest.getPickerScreenState()
+            runCurrent()
+
+            assertEquals(
+                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
+                result,
+            )
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun controllerDndNotAvailable_pickerStateHidden() =
+        testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(false)
 
@@ -105,13 +138,33 @@
             // then
             assertEquals(
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
-                result
+                result,
             )
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
     fun dndAvailable_pickerStateVisible() =
         testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            runCurrent()
+
+            val result = underTest.getPickerScreenState()
+            runCurrent()
+
+            assertThat(result)
+                .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+            val defaultPickerState =
+                result as KeyguardQuickAffordanceConfig.PickerScreenState.Default
+            assertThat(defaultPickerState.configureIntent).isNotNull()
+            assertThat(defaultPickerState.configureIntent?.action)
+                .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun controllerDndAvailable_pickerStateVisible() =
+        testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(true)
 
@@ -129,7 +182,27 @@
         }
 
     @Test
-    fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_dndModeIsNotOff_setToOff() =
+        testScope.runTest {
+            val currentModes by collectLastValue(zenModeRepository.modes)
+
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2)
+            collectLastValue(underTest.lockScreenState)
+            runCurrent()
+
+            val result = underTest.onTriggered(null)
+            runCurrent()
+
+            val dndMode = currentModes!!.single()
+            assertThat(dndMode.isActive).isFalse()
+            assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
         testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -140,11 +213,12 @@
 
             // when
             val result = underTest.onTriggered(null)
+
             verify(zenModeController)
                 .setZen(
                     spyZenMode.capture(),
                     spyConditionId.capture(),
-                    eq(DoNotDisturbQuickAffordanceConfig.TAG)
+                    eq(DoNotDisturbQuickAffordanceConfig.TAG),
                 )
 
             // then
@@ -154,7 +228,28 @@
         }
 
     @Test
-    fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() =
+        testScope.runTest {
+            val currentModes by collectLastValue(zenModeRepository.modes)
+
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER)
+            collectLastValue(underTest.lockScreenState)
+            runCurrent()
+
+            val result = underTest.onTriggered(null)
+            runCurrent()
+
+            val dndMode = currentModes!!.single()
+            assertThat(dndMode.isActive).isTrue()
+            assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull()
+            assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
         testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -169,7 +264,7 @@
                 .setZen(
                     spyZenMode.capture(),
                     spyConditionId.capture(),
-                    eq(DoNotDisturbQuickAffordanceConfig.TAG)
+                    eq(DoNotDisturbQuickAffordanceConfig.TAG),
                 )
 
             // then
@@ -179,7 +274,27 @@
         }
 
     @Test
-    fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() =
+        testScope.runTest {
+            val currentModes by collectLastValue(zenModeRepository.modes)
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900)
+            runCurrent()
+
+            val result = underTest.onTriggered(null)
+            runCurrent()
+
+            assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+            val dndMode = currentModes!!.single()
+            assertThat(dndMode.isActive).isTrue()
+            assertThat(zenModeRepository.getModeActiveDuration(dndMode.id))
+                .isEqualTo(Duration.ofMinutes(-900))
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
         testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -194,7 +309,7 @@
                 .setZen(
                     spyZenMode.capture(),
                     spyConditionId.capture(),
-                    eq(DoNotDisturbQuickAffordanceConfig.TAG)
+                    eq(DoNotDisturbQuickAffordanceConfig.TAG),
                 )
 
             // then
@@ -204,7 +319,28 @@
         }
 
     @Test
-    fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() =
+        testScope.runTest {
+            val expandable: Expandable = mock()
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
+            whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+            collectLastValue(underTest.lockScreenState)
+            runCurrent()
+
+            val result = underTest.onTriggered(expandable)
+
+            assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
+            assertEquals(
+                expandable,
+                (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
+            )
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
         testScope.runTest {
             // given
             val expandable: Expandable = mock()
@@ -222,13 +358,31 @@
             assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
             assertEquals(
                 expandable,
-                (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable
+                (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
             )
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
     fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
         testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            val valueSnapshot = collectLastValue(underTest.lockScreenState)
+            val secondLastValue = valueSnapshot()
+            runCurrent()
+
+            deviceProvisioningRepository.setDeviceProvisioned(false)
+            runCurrent()
+            val lastValue = valueSnapshot()
+
+            assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+            assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
+        testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(true)
             val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor()
@@ -246,7 +400,44 @@
         }
 
     @Test
-    fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() =
+        testScope.runTest {
+            val lockScreenState by collectLastValue(underTest.lockScreenState)
+
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            runCurrent()
+
+            assertThat(lockScreenState)
+                .isEqualTo(
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                        Icon.Resource(
+                            R.drawable.qs_dnd_icon_off,
+                            ContentDescription.Resource(R.string.dnd_is_off),
+                        ),
+                        ActivationState.Inactive,
+                    )
+                )
+
+            zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+            runCurrent()
+
+            assertThat(lockScreenState)
+                .isEqualTo(
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                        Icon.Resource(
+                            R.drawable.qs_dnd_icon_on,
+                            ContentDescription.Resource(R.string.dnd_is_on),
+                        ),
+                        ActivationState.Active,
+                    )
+                )
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
         testScope.runTest {
             // given
             whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -265,9 +456,9 @@
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     Icon.Resource(
                         R.drawable.qs_dnd_icon_off,
-                        ContentDescription.Resource(R.string.dnd_is_off)
+                        ContentDescription.Resource(R.string.dnd_is_off),
                     ),
-                    ActivationState.Inactive
+                    ActivationState.Inactive,
                 ),
                 secondLastValue,
             )
@@ -275,9 +466,9 @@
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     Icon.Resource(
                         R.drawable.qs_dnd_icon_on,
-                        ContentDescription.Resource(R.string.dnd_is_on)
+                        ContentDescription.Resource(R.string.dnd_is_on),
                     ),
-                    ActivationState.Active
+                    ActivationState.Active,
                 ),
                 lastValue,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index c18deb1..fac9312 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -14,22 +14,6 @@
  * limitations under the License.
  */
 
-/*
- * 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.keyguard.domain.interactor
 
 import android.os.PowerManager
@@ -47,7 +31,6 @@
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -129,7 +112,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.DOZING,
-                testScope
+                testScope,
             )
             kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
             reset(transitionRepository)
@@ -145,10 +128,7 @@
 
             // Under default conditions, we should transition to LOCKSCREEN when waking up.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -166,10 +146,7 @@
             // If dreaming is possible and communal is available, then we should transition to
             // GLANCEABLE_HUB when waking up due to power button press.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
         }
 
     @Test
@@ -186,8 +163,7 @@
 
             // If dreaming is possible and communal is available, then we should transition to
             // GLANCEABLE_HUB when waking up due to power button press.
-            verify(kosmos.fakeCommunalSceneRepository)
-                .changeScene(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+            verify(kosmos.fakeCommunalSceneRepository).snapToScene(CommunalScenes.Communal)
         }
 
     @Test
@@ -204,10 +180,7 @@
             // If dreaming is NOT possible but communal is available, then we should transition to
             // LOCKSCREEN when waking up due to power button press.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -224,10 +197,7 @@
             // If dreaming is possible but communal is NOT available, then we should transition to
             // LOCKSCREEN when waking up due to power button press.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -245,10 +215,7 @@
 
             // Under default conditions, we should transition to LOCKSCREEN when waking up.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
         }
 
     @Test
@@ -261,10 +228,7 @@
 
             // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.OCCLUDED,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
         }
 
     @Test
@@ -282,10 +246,7 @@
 
             // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DOZING,
-                    to = KeyguardState.OCCLUDED,
-                )
+                .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 40fb769..614d51e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -39,6 +39,7 @@
 import android.app.IActivityManager;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
@@ -53,6 +54,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -466,6 +468,32 @@
         assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0);
     }
 
+    @Test
+    @EnableSceneContainer
+    public void configChanged_boundsUpdate() {
+        when(mNotificationShadeWindowView.getWidth()).thenReturn(1600);
+        when(mNotificationShadeWindowView.getHeight()).thenReturn(800);
+        when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE);
+        Configuration newConfig = new Configuration();
+        // swap width and height in new bounds to simulate auto-rotate
+        newConfig.windowConfiguration.setBounds(new Rect(0, 0, 800, 1600));
+        mNotificationShadeWindowController.onConfigChanged(newConfig);
+        verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), any());
+    }
+
+    @Test
+    @EnableSceneContainer
+    public void configChanged_boundsDontUpdate() {
+        when(mNotificationShadeWindowView.getWidth()).thenReturn(1600);
+        when(mNotificationShadeWindowView.getHeight()).thenReturn(800);
+        when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE);
+        Configuration newConfig = new Configuration();
+        // same bounds as view's current bounds
+        newConfig.windowConfiguration.setBounds(new Rect(0, 0, 1600, 800));
+        mNotificationShadeWindowController.onConfigChanged(newConfig);
+        verify(mWindowManager, never()).updateViewLayout(any(), any());
+    }
+
     private void setKeyguardShowing() {
         mNotificationShadeWindowController.setKeyguardShowing(true);
         mNotificationShadeWindowController.setKeyguardGoingAway(false);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index fb32855..0f6dc07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.policy.domain.interactor
 
 import android.app.AutomaticZenRule
+import android.app.Flags
 import android.app.NotificationManager.Policy
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import android.provider.Settings.Secure.ZEN_DURATION
 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
@@ -32,6 +34,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -50,10 +53,31 @@
     private val testScope = kosmos.testScope
     private val zenModeRepository = kosmos.fakeZenModeRepository
     private val settingsRepository = kosmos.secureSettingsRepository
+    private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
 
     private val underTest = kosmos.zenModeInteractor
 
     @Test
+    fun isZenAvailable_off() =
+        testScope.runTest {
+            val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+            deviceProvisioningRepository.setDeviceProvisioned(false)
+            runCurrent()
+
+            assertThat(isZenAvailable).isFalse()
+        }
+
+    @Test
+    fun isZenAvailable_on() =
+        testScope.runTest {
+            val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            runCurrent()
+
+            assertThat(isZenAvailable).isTrue()
+        }
+
+    @Test
     fun isZenModeEnabled_off() =
         testScope.runTest {
             val enabled by collectLastValue(underTest.isZenModeEnabled)
@@ -337,4 +361,22 @@
             runCurrent()
             assertThat(mainActiveMode).isNull()
         }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    fun dndMode_flows() =
+        testScope.runTest {
+            val dndMode by collectLastValue(underTest.dndMode)
+
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            runCurrent()
+
+            assertThat(dndMode!!.isActive).isFalse()
+
+            zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+            runCurrent()
+
+            assertThat(dndMode!!.isActive).isTrue()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 21a45ec..9dcbe1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -616,6 +616,71 @@
     }
 
     @Test
+    fun angleDecreaseAfterCancelAnimation_emitsStartClosingEvent() {
+        setFoldState(folded = true)
+        sendHingeAngleEvent(0)
+        foldUpdates.clear()
+
+        setFoldState(folded = false)
+        rotationListener.value.onRotationChanged(1)
+        sendHingeAngleEvent(180)
+        screenOnStatusProvider.notifyScreenTurningOn()
+        screenOnStatusProvider.notifyScreenTurnedOn()
+
+        // Start folding
+        (180 downTo 60).forEach {
+            sendHingeAngleEvent(it)
+        }
+        // Stopped folding and simulate timeout in this posture
+        simulateTimeout()
+
+        assertThat(foldUpdates)
+            .containsExactly(
+                FOLD_UPDATE_START_OPENING, // unfolded
+                FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation
+                FOLD_UPDATE_START_CLOSING, // start closing the device
+                FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state
+            )
+
+    }
+
+    @Test
+    fun angleIncreaseDecreaseAfterHalfUnfold_emitsStartClosingEvent() {
+        setFoldState(folded = true)
+        sendHingeAngleEvent(0)
+        foldUpdates.clear()
+
+        setFoldState(folded = false)
+        sendHingeAngleEvent(90)
+        screenOnStatusProvider.notifyScreenTurningOn()
+        screenOnStatusProvider.notifyScreenTurnedOn()
+
+        // Stopped folding and simulate timeout in this posture
+        simulateTimeout()
+
+        // Unfold further
+        (90 until 180).forEach {
+            sendHingeAngleEvent(it)
+        }
+        // Start folding
+        (180 downTo 90).forEach {
+            sendHingeAngleEvent(it)
+        }
+
+        // Stopped folding and simulate timeout in this posture
+        simulateTimeout()
+
+        assertThat(foldUpdates)
+            .containsExactly(
+                FOLD_UPDATE_START_OPENING, // unfolded
+                FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation
+                FOLD_UPDATE_START_CLOSING, // start closing the device
+                FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state
+            )
+
+    }
+
+    @Test
     fun onUnfold_onSmallScreen_emitsStartOpening() {
         // the new display state might arrive later, so it shouldn't be used to decide to send the
         // start opening event, but only for the closing.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index 19e7537..b8c30fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -76,11 +76,7 @@
     /** Deliver MSDL feedback when the delete key of the pin bouncer is pressed */
     fun playDeleteKeyPressFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_DELETE)
 
-    /**
-     * Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed
-     *
-     * @return whether MSDL feedback is allowed to play.
-     */
+    /** Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed. */
     fun playDeleteKeyLongPressedFeedback() = msdlPlayer.get().playToken(MSDLToken.LONG_PRESS)
 
     /** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index c67b354..873d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.channels.Channel
@@ -42,6 +43,7 @@
 
     /** Name to use for performance tracing purposes. */
     val traceName: String,
+    protected val bouncerHapticPlayer: BouncerHapticPlayer? = null,
 ) : ExclusiveActivatable() {
 
     private val _animateFailure = MutableStateFlow(false)
@@ -80,6 +82,8 @@
                 return@collectLatest
             }
 
+            performAuthenticationHapticFeedback(authenticationResult)
+
             _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
             clearInput()
         }
@@ -112,20 +116,23 @@
     /** Returns the input entered so far. */
     protected abstract fun getInput(): List<Any>
 
+    /** Perform authentication result haptics */
+    private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
+        if (result == AuthenticationResult.SKIPPED) return
+
+        bouncerHapticPlayer?.playAuthenticationFeedback(
+            authenticationSucceeded = result == AuthenticationResult.SUCCEEDED
+        )
+    }
+
     /**
      * Attempts to authenticate the user using the current input value.
      *
      * @see BouncerInteractor.authenticate
      */
-    protected fun tryAuthenticate(
-        input: List<Any> = getInput(),
-        useAutoConfirm: Boolean = false,
-    ) {
+    protected fun tryAuthenticate(input: List<Any> = getInput(), useAutoConfirm: Boolean = false) {
         authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
     }
 
-    private data class AuthenticationRequest(
-        val input: List<Any>,
-        val useAutoConfirm: Boolean,
-    )
+    private data class AuthenticationRequest(val input: List<Any>, val useAutoConfirm: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 0aada06..0bcb58d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.qualifiers.Application
@@ -61,6 +62,7 @@
     private val pinViewModelFactory: PinBouncerViewModel.Factory,
     private val patternViewModelFactory: PatternBouncerViewModel.Factory,
     private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+    private val bouncerHapticPlayer: BouncerHapticPlayer,
 ) : ExclusiveActivatable() {
     private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
     val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -162,10 +164,7 @@
             }
 
             launch {
-                combine(
-                        userSwitcher.users,
-                        userSwitcher.menu,
-                    ) { users, actions ->
+                combine(userSwitcher.users, userSwitcher.menu) { users, actions ->
                         users.map { user ->
                             UserSwitcherDropdownItemViewModel(
                                 icon = Icon.Loaded(user.image, contentDescription = null),
@@ -178,7 +177,7 @@
                                     icon =
                                         Icon.Resource(
                                             action.iconResourceId,
-                                            contentDescription = null
+                                            contentDescription = null,
                                         ),
                                     text = Text.Resource(action.textResourceId),
                                     onClick = action.onClicked,
@@ -226,7 +225,7 @@
     }
 
     private fun getChildViewModel(
-        authenticationMethod: AuthenticationMethodModel,
+        authenticationMethod: AuthenticationMethodModel
     ): AuthMethodBouncerViewModel? {
         // If the current child view-model matches the authentication method, reuse it instead of
         // creating a new instance.
@@ -241,12 +240,14 @@
                     authenticationMethod = authenticationMethod,
                     onIntentionalUserInput = ::onIntentionalUserInput,
                     isInputEnabled = isInputEnabled,
+                    bouncerHapticPlayer = bouncerHapticPlayer,
                 )
             is AuthenticationMethodModel.Sim ->
                 pinViewModelFactory.create(
                     authenticationMethod = authenticationMethod,
                     onIntentionalUserInput = ::onIntentionalUserInput,
                     isInputEnabled = isInputEnabled,
+                    bouncerHapticPlayer = bouncerHapticPlayer,
                 )
             is AuthenticationMethodModel.Password ->
                 passwordViewModelFactory.create(
@@ -257,6 +258,7 @@
                 patternViewModelFactory.create(
                     onIntentionalUserInput = ::onIntentionalUserInput,
                     isInputEnabled = isInputEnabled,
+                    bouncerHapticPlayer = bouncerHapticPlayer,
                 )
             else -> null
         }
@@ -317,10 +319,7 @@
         return when {
             // The wipe dialog takes priority over the lockout dialog.
             wipeText != null ->
-                DialogViewModel(
-                    text = wipeText,
-                    onDismiss = { wipeDialogMessage.value = null },
-                )
+                DialogViewModel(text = wipeText, onDismiss = { wipeDialogMessage.value = null })
             lockoutText != null ->
                 DialogViewModel(
                     text = lockoutText,
@@ -338,7 +337,7 @@
     fun onKeyEvent(keyEvent: KeyEvent): Boolean {
         return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
             keyEvent.type,
-            keyEvent.nativeKeyEvent.keyCode
+            keyEvent.nativeKeyEvent.keyCode,
         ) ?: false
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 0a866b4..158f102 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,9 +18,11 @@
 
 import android.content.Context
 import android.util.TypedValue
+import android.view.View
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.res.R
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -35,7 +37,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
@@ -44,6 +45,7 @@
 constructor(
     private val applicationContext: Context,
     interactor: BouncerInteractor,
+    @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
     @Assisted isInputEnabled: StateFlow<Boolean>,
     @Assisted private val onIntentionalUserInput: () -> Unit,
 ) :
@@ -51,6 +53,7 @@
         interactor = interactor,
         isInputEnabled = isInputEnabled,
         traceName = "PatternBouncerViewModel",
+        bouncerHapticPlayer = bouncerHapticPlayer,
     ) {
 
     /** The number of columns in the dot grid. */
@@ -190,14 +193,7 @@
     private fun defaultDots(): List<PatternDotViewModel> {
         return buildList {
             (0 until columnCount).forEach { x ->
-                (0 until rowCount).forEach { y ->
-                    add(
-                        PatternDotViewModel(
-                            x = x,
-                            y = y,
-                        )
-                    )
-                }
+                (0 until rowCount).forEach { y -> add(PatternDotViewModel(x = x, y = y)) }
             }
         }
     }
@@ -207,14 +203,17 @@
         applicationContext.resources.getValue(
             com.android.internal.R.dimen.lock_pattern_dot_hit_factor,
             outValue,
-            true
+            true,
         )
         max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
     }
 
+    fun performDotFeedback(view: View?) = bouncerHapticPlayer?.playPatternDotFeedback(view)
+
     @AssistedFactory
     interface Factory {
         fun create(
+            bouncerHapticPlayer: BouncerHapticPlayer,
             isInputEnabled: StateFlow<Boolean>,
             onIntentionalUserInput: () -> Unit,
         ): PatternBouncerViewModel
@@ -231,7 +230,7 @@
  */
 private fun PatternDotViewModel.isOnLineSegment(
     first: PatternDotViewModel,
-    second: PatternDotViewModel
+    second: PatternDotViewModel,
 ): Boolean {
     val anotherPoint = this
     // No need to consider any points outside the bounds of two end points
@@ -253,14 +252,8 @@
     return (this in a..b) || (this in b..a)
 }
 
-data class PatternDotViewModel(
-    val x: Int,
-    val y: Int,
-) {
+data class PatternDotViewModel(val x: Int, val y: Int) {
     fun toCoordinate(): AuthenticationPatternCoordinate {
-        return AuthenticationPatternCoordinate(
-            x = x,
-            y = y,
-        )
+        return AuthenticationPatternCoordinate(x = x, y = y)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index da29c62..0cb4260 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -19,12 +19,14 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
+import android.view.HapticFeedbackConstants
 import android.view.KeyEvent.KEYCODE_0
 import android.view.KeyEvent.KEYCODE_9
 import android.view.KeyEvent.KEYCODE_DEL
 import android.view.KeyEvent.KEYCODE_NUMPAD_0
 import android.view.KeyEvent.KEYCODE_NUMPAD_9
 import android.view.KeyEvent.isConfirmKey
+import android.view.View
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType
 import com.android.keyguard.PinShapeAdapter
@@ -32,6 +34,7 @@
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.res.R
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -56,6 +59,7 @@
     applicationContext: Context,
     interactor: BouncerInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
+    @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
     @Assisted isInputEnabled: StateFlow<Boolean>,
     @Assisted private val onIntentionalUserInput: () -> Unit,
     @Assisted override val authenticationMethod: AuthenticationMethodModel,
@@ -64,6 +68,7 @@
         interactor = interactor,
         isInputEnabled = isInputEnabled,
         traceName = "PinBouncerViewModel",
+        bouncerHapticPlayer = bouncerHapticPlayer,
     ) {
     /**
      * Whether the sim-related UI in the pin view is showing.
@@ -126,10 +131,9 @@
                     .collect { _hintedPinLength.value = it }
             }
             launch {
-                combine(
-                        mutablePinInput,
-                        interactor.isAutoConfirmEnabled,
-                    ) { mutablePinEntries, isAutoConfirmEnabled ->
+                combine(mutablePinInput, interactor.isAutoConfirmEnabled) {
+                        mutablePinEntries,
+                        isAutoConfirmEnabled ->
                         computeBackspaceButtonAppearance(
                             pinInput = mutablePinEntries,
                             isAutoConfirmEnabled = isAutoConfirmEnabled,
@@ -183,8 +187,22 @@
         mutablePinInput.value = mutablePinInput.value.deleteLast()
     }
 
+    fun onBackspaceButtonPressed(view: View?) {
+        if (bouncerHapticPlayer?.isEnabled == true) {
+            bouncerHapticPlayer.playDeleteKeyPressFeedback()
+        } else {
+            view?.performHapticFeedback(
+                HapticFeedbackConstants.VIRTUAL_KEY,
+                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+            )
+        }
+    }
+
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
+        if (bouncerHapticPlayer?.isEnabled == true) {
+            bouncerHapticPlayer.playDeleteKeyLongPressedFeedback()
+        }
         clearInput()
     }
 
@@ -266,13 +284,24 @@
         }
     }
 
-    /** Notifies that the user has pressed down on a digit button. */
-    fun onDigitButtonDown() {
+    /**
+     * Notifies that the user has pressed down on a digit button. This function also performs haptic
+     * feedback on the view.
+     */
+    fun onDigitButtonDown(view: View?) {
         if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
             // Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button
             // touch.
             super.onDown()
         }
+        if (bouncerHapticPlayer?.isEnabled == true) {
+            bouncerHapticPlayer.playNumpadKeyFeedback()
+        } else {
+            view?.performHapticFeedback(
+                HapticFeedbackConstants.VIRTUAL_KEY,
+                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+            )
+        }
     }
 
     @AssistedFactory
@@ -281,6 +310,7 @@
             isInputEnabled: StateFlow<Boolean>,
             onIntentionalUserInput: () -> Unit,
             authenticationMethod: AuthenticationMethodModel,
+            bouncerHapticPlayer: BouncerHapticPlayer,
         ): PinBouncerViewModel
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 769976e..ae4b679d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -282,6 +282,8 @@
         return !mRecentKeyEvents.isEmpty();
     }
 
+    // Deprecated in favor of {@code isTouchScreenSource}, b/329221787
+    @Deprecated
     public boolean isFromTrackpad() {
         if (Flags.nonTouchscreenDevicesBypassFalsing()) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index c7a47b1..1ada56d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -30,6 +30,7 @@
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.os.Build;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -37,6 +38,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.user.utils.UserScopedService;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -67,13 +69,13 @@
     public ClipboardListener(Context context,
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
             ClipboardToast clipboardToast,
-            ClipboardManager clipboardManager,
+            UserScopedService<ClipboardManager> clipboardManager,
             KeyguardManager keyguardManager,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mClipboardToast = clipboardToast;
-        mClipboardManager = clipboardManager;
+        mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT);
         mKeyguardManager = keyguardManager;
         mUiEventLogger = uiEventLogger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 6e01393..08a7c39 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -19,11 +19,13 @@
 import android.provider.Settings
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.shared.model.EditModeState
@@ -84,6 +86,7 @@
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
+    private val uiEventLogger: UiEventLogger,
 ) : CoreStartable {
     private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
 
@@ -146,7 +149,7 @@
                 screenTimeout =
                     systemSettings.getInt(
                         Settings.System.SCREEN_OFF_TIMEOUT,
-                        DEFAULT_SCREEN_TIMEOUT
+                        DEFAULT_SCREEN_TIMEOUT,
                     )
             }
             .launchIn(bgScope)
@@ -160,7 +163,7 @@
             combine(
                     communalSceneInteractor.currentScene,
                     // Emit a value on start so the combine starts.
-                    communalInteractor.userActivity.emitOnStart()
+                    communalInteractor.userActivity.emitOnStart(),
                 ) { scene, _ ->
                     // Only timeout if we're on the hub is open.
                     scene == CommunalScenes.Communal
@@ -184,6 +187,7 @@
                             CommunalScenes.Blank,
                             "dream started after timeout",
                         )
+                        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
                     }
                 }
         }
@@ -212,6 +216,7 @@
                             newScene = CommunalScenes.Blank,
                             loggingReason = "hub timeout",
                         )
+                        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
                     }
                     timeoutJob = null
                 }
@@ -219,7 +224,7 @@
     }
 
     private suspend fun determineSceneAfterTransition(
-        lastStartedTransition: TransitionStep,
+        lastStartedTransition: TransitionStep
     ): Pair<SceneKey, TransitionKey>? {
         val to = lastStartedTransition.to
         val from = lastStartedTransition.from
@@ -251,9 +256,8 @@
                 Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
             }
             from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
-                // Make sure the communal hub is showing (immediately, not fading in) when
-                // transitioning from dozing to hub.
-                Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+                // Make sure the communal hub is showing when transitioning from dozing to hub.
+                Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
             }
             else -> null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index 11fb233..78156db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -30,6 +30,4 @@
     val ToEditMode = TransitionKey("ToEditMode")
     /** Transition to the glanceable hub after exiting edit mode */
     val FromEditMode = TransitionKey("FromEditMode")
-    /** Immediately transitions without any delay */
-    val Immediately = TransitionKey("Immediately")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8818c3a..8f913ff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -733,9 +733,8 @@
     }
 
     @Provides
-    @Singleton
-    static ClipboardManager provideClipboardManager(Context context) {
-        return context.getSystemService(ClipboardManager.class);
+    static UserScopedService<ClipboardManager> provideClipboardManager(Context context) {
+        return new UserScopedServiceImpl<>(context, ClipboardManager.class);
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index e17e530..5259c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -26,7 +26,9 @@
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
 import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.TrustInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
@@ -36,6 +38,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -57,6 +60,7 @@
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
     private val systemPropertiesHelper: SystemPropertiesHelper,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
 
     private val deviceUnlockSource =
@@ -74,7 +78,7 @@
             trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
             authenticationInteractor.onAuthenticationResult
                 .filter { it }
-                .map { DeviceUnlockSource.BouncerInput }
+                .map { DeviceUnlockSource.BouncerInput },
         )
 
     private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
@@ -170,10 +174,20 @@
                     combine(
                             powerInteractor.isAsleep,
                             isInLockdown,
-                            ::Pair,
+                            keyguardTransitionInteractor
+                                .transitionValue(KeyguardState.AOD)
+                                .map { it == 1f }
+                                .distinctUntilChanged(),
+                            ::Triple,
                         )
-                        .flatMapLatestConflated { (isAsleep, isInLockdown) ->
-                            if (isAsleep || isInLockdown) {
+                        .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
+                            val isForceLocked =
+                                when {
+                                    isAsleep && !isAod -> true
+                                    isInLockdown -> true
+                                    else -> false
+                                }
+                            if (isForceLocked) {
                                 flowOf(DeviceUnlockStatus(false, null))
                             } else {
                                 deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
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 e50c05c..e09e198 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.QSLog
+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
@@ -44,12 +45,12 @@
  * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
  * @property[effectDuration] The duration of the effect in ms.
  */
-// TODO(b/332902869): In addition from being injectable, we can consider making it a singleton
 class QSLongPressEffect
 @Inject
 constructor(
     private val vibratorHelper: VibratorHelper?,
     private val keyguardStateController: KeyguardStateController,
+    private val falsingManager: FalsingManager,
     @QSLog private val logBuffer: LogBuffer,
 ) {
 
@@ -72,7 +73,7 @@
     private val durations =
         vibratorHelper?.getPrimitiveDurations(
             VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-            VibrationEffect.Composition.PRIMITIVE_SPIN
+            VibrationEffect.Composition.PRIMITIVE_SPIN,
         )
 
     private var longPressHint: VibrationEffect? = null
@@ -152,15 +153,27 @@
         logEvent(qsTile?.tileSpec, state, "animation completed")
         when (state) {
             State.RUNNING_FORWARD -> {
-                vibrate(snapEffect)
-                if (keyguardStateController.isUnlocked) {
-                    setState(State.LONG_CLICKED)
-                } else {
+                val wasFalseLongTap = falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+                if (wasFalseLongTap) {
                     callback?.onResetProperties()
                     setState(State.IDLE)
+                    logEvent(qsTile?.tileSpec, state, "false long click. No action triggered")
+                } else if (keyguardStateController.isUnlocked) {
+                    vibrate(snapEffect)
+                    setState(State.LONG_CLICKED)
+                    qsTile?.longClick(expandable)
+                    logEvent(qsTile?.tileSpec, state, "long click action triggered")
+                } else {
+                    vibrate(snapEffect)
+                    callback?.onResetProperties()
+                    setState(State.IDLE)
+                    qsTile?.longClick(expandable)
+                    logEvent(
+                        qsTile?.tileSpec,
+                        state,
+                        "properties reset and long click action triggered",
+                    )
                 }
-                logEvent(qsTile?.tileSpec, state, "long click action triggered")
-                qsTile?.longClick(expandable)
             }
             State.RUNNING_BACKWARDS_FROM_UP -> {
                 callback?.onEffectFinishedReversing()
@@ -236,7 +249,7 @@
             LongPressHapticBuilder.createLongPressHint(
                 durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
                 durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
-                effectDuration
+                effectDuration,
             )
         setState(State.IDLE)
         return true
@@ -265,7 +278,7 @@
                 }
 
                 override fun dialogTransitionController(
-                    cuj: DialogCuj?,
+                    cuj: DialogCuj?
                 ): DialogTransitionAnimator.Controller? =
                     DialogTransitionAnimator.Controller.fromView(view, cuj)
             }
@@ -298,7 +311,7 @@
                 str2 = event
                 str3 = state.name
             },
-            { "[long-press effect on $str1 tile] $str2 on state: $str3" }
+            { "[long-press effect on $str1 tile] $str2 on state: $str3" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
index 9525174..48f5cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
@@ -16,27 +16,27 @@
 
 package com.android.systemui.inputdevice.tutorial
 
+import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen as KeyboardTouchpadTutorialScreen
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.log.dagger.InputDeviceTutorialLog
-import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen
-import com.google.errorprone.annotations.CompileTimeConstant
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen as TouchpadTutorialScreen
 import javax.inject.Inject
 
 private const val TAG = "InputDeviceTutorial"
 
 class InputDeviceTutorialLogger
 @Inject
-constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) {
+constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) :
+    ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
-    fun log(@CompileTimeConstant s: String) {
-        buffer.log(TAG, LogLevel.INFO, message = s)
-    }
-
-    fun logGoingToScreen(screen: Screen, context: TutorialContext) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
+    fun logGoingToScreen(screen: TouchpadTutorialScreen, context: TutorialContext) {
+        logInfo(
             {
                 str1 = screen.toString()
                 str2 = context.string
@@ -46,7 +46,58 @@
     }
 
     fun logCloseTutorial(context: TutorialContext) {
-        buffer.log(TAG, LogLevel.INFO, { str1 = context.string }, { "Closing $str1" })
+        logInfo({ str1 = context.string }, { "Closing $str1" })
+    }
+
+    fun logOpenTutorial(context: TutorialContext) {
+        logInfo({ str1 = context.string }, { "Opening $str1" })
+    }
+
+    fun logNextScreenMissingHardware(nextScreen: KeyboardTouchpadTutorialScreen) {
+        buffer.log(
+            TAG,
+            LogLevel.WARNING,
+            { str1 = nextScreen.toString() },
+            { "next screen should be $str1 but required hardware is missing" }
+        )
+    }
+
+    fun logNextScreen(nextScreen: KeyboardTouchpadTutorialScreen) {
+        logInfo({ str1 = nextScreen.toString() }, { "going to $str1 screen" })
+    }
+
+    fun logNewConnectionState(connectionState: ConnectionState) {
+        logInfo(
+            {
+                bool1 = connectionState.touchpadConnected
+                bool2 = connectionState.keyboardConnected
+            },
+            { "Received connection state: touchpad connected: $bool1 keyboard connected: $bool2" }
+        )
+    }
+
+    fun logMovingBetweenScreens(
+        previousScreen: KeyboardTouchpadTutorialScreen?,
+        currentScreen: KeyboardTouchpadTutorialScreen
+    ) {
+        logInfo(
+            {
+                str1 = previousScreen?.toString() ?: "NO_SCREEN"
+                str2 = currentScreen.toString()
+            },
+            { "Moving from $str1 screen to $str2 screen" }
+        )
+    }
+
+    fun logGoingBack(previousScreen: KeyboardTouchpadTutorialScreen) {
+        logInfo({ str1 = previousScreen.toString() }, { "Going back to $str1 screen" })
+    }
+
+    private inline fun logInfo(
+        messageInitializer: MessageInitializer,
+        noinline messagePrinter: MessagePrinter
+    ) {
+        buffer.log(TAG, LogLevel.INFO, messageInitializer, messagePrinter)
     }
 
     enum class TutorialContext(val string: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 1adc285..c130c6c 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -28,6 +28,8 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
 import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
 import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
@@ -48,6 +50,7 @@
 constructor(
     private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
     private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+    private val logger: InputDeviceTutorialLogger,
 ) : ComponentActivity() {
 
     companion object {
@@ -74,6 +77,7 @@
         lifecycleScope.launch {
             vm.closeActivity.collect { finish ->
                 if (finish) {
+                    logger.logCloseTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
                     finish()
                 }
             }
@@ -81,6 +85,9 @@
         setContent {
             PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
         }
+        if (savedInstanceState == null) {
+            logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
index 315c102..5cf1967 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -22,6 +22,7 @@
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
 import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
 import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
@@ -47,6 +48,7 @@
     private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
     private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
     private val hasTouchpadTutorialScreens: Boolean,
+    private val logger: InputDeviceTutorialLogger,
     handle: SavedStateHandle
 ) : ViewModel(), DefaultLifecycleObserver {
 
@@ -68,7 +70,10 @@
 
     init {
         viewModelScope.launch {
-            keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it }
+            keyboardTouchpadConnectionInteractor.connectionState.collect {
+                logger.logNewConnectionState(connectionState)
+                connectionState = it
+            }
         }
 
         viewModelScope.launch {
@@ -89,7 +94,14 @@
         viewModelScope.launch {
             // close activity if screen requires touchpad but we don't have it. This can only happen
             // when current sysui build doesn't contain touchpad module dependency
-            _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true }
+            _screen
+                .filterNot { it.canBeShown() }
+                .collect {
+                    logger.e(
+                        "Touchpad is connected but touchpad module is missing, something went wrong"
+                    )
+                    _closeActivity.value = true
+                }
         }
     }
 
@@ -114,11 +126,14 @@
             if (requiredHardwarePresent(nextScreen)) {
                 break
             }
+            logger.logNextScreenMissingHardware(nextScreen)
             nextScreen = nextScreen.next()
         }
         if (nextScreen == null) {
+            logger.d("Final screen reached, closing tutorial")
             _closeActivity.value = true
         } else {
+            logger.logNextScreen(nextScreen)
             _screen.value = nextScreen
             screensBackStack.add(nextScreen)
         }
@@ -127,6 +142,7 @@
     private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens
 
     private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) {
+        logger.logMovingBetweenScreens(previousScreen, currentScreen)
         if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return
         previousScreen?.let { clearDeviceStateForScreen(it) }
         when (currentScreen.requiredHardware) {
@@ -153,6 +169,7 @@
             _closeActivity.value = true
         } else {
             screensBackStack.removeLast()
+            logger.logGoingBack(screensBackStack.last())
             _screen.value = screensBackStack.last()
         }
     }
@@ -162,6 +179,7 @@
     constructor(
         private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
         private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor,
+        private val logger: InputDeviceTutorialLogger,
         @Assisted private val hasTouchpadTutorialScreens: Boolean,
     ) : AbstractSavedStateViewModelFactory() {
 
@@ -180,6 +198,7 @@
                 gesturesInteractor,
                 keyboardTouchpadConnected,
                 hasTouchpadTutorialScreens,
+                logger,
                 handle
             )
                 as T
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 406b9f6..be87334 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -25,7 +25,9 @@
 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
 import android.service.notification.ZenModeConfig
+import android.util.Log
 import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.ZenMode
 import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -35,30 +37,38 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.modes.shared.ModesUi
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
 class DoNotDisturbQuickAffordanceConfig
 constructor(
     private val context: Context,
     private val controller: ZenModeController,
+    private val interactor: ZenModeInteractor,
     private val secureSettings: SecureSettings,
     private val userTracker: UserTracker,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val backgroundScope: CoroutineScope,
     private val testConditionId: Uri?,
     testDialog: EnableZenModeDialog?,
 ) : KeyguardQuickAffordanceConfig {
@@ -67,15 +77,45 @@
     constructor(
         context: Context,
         controller: ZenModeController,
+        interactor: ZenModeInteractor,
         secureSettings: SecureSettings,
         userTracker: UserTracker,
         @Background backgroundDispatcher: CoroutineDispatcher,
-    ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null)
+        @Background backgroundScope: CoroutineScope,
+    ) : this(
+        context,
+        controller,
+        interactor,
+        secureSettings,
+        userTracker,
+        backgroundDispatcher,
+        backgroundScope,
+        null,
+        null,
+    )
 
-    private var dndMode: Int = 0
-    private var isAvailable = false
+    private var zenMode: Int = 0
+    private var oldIsAvailable = false
     private var settingsValue: Int = 0
 
+    private val dndMode: StateFlow<ZenMode?> by lazy {
+        ModesUi.assertInNewMode()
+        interactor.dndMode.stateIn(
+            scope = backgroundScope,
+            started = SharingStarted.Eagerly,
+            initialValue = null,
+        )
+    }
+
+    private val isAvailable: StateFlow<Boolean> by lazy {
+        ModesUi.assertInNewMode()
+        interactor.isZenAvailable.stateIn(
+            scope = backgroundScope,
+            started = SharingStarted.Eagerly,
+            initialValue = false,
+        )
+    }
+
     private val conditionUri: Uri
         get() =
             testConditionId
@@ -104,42 +144,68 @@
     override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
-        combine(
-            conflatedCallbackFlow {
-                val callback =
-                    object : ZenModeController.Callback {
-                        override fun onZenChanged(zen: Int) {
-                            dndMode = zen
-                            trySendWithFailureLogging(updateState(), TAG)
+        if (ModesUi.isEnabled) {
+            combine(isAvailable, dndMode) { isAvailable, dndMode ->
+                if (!isAvailable) {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+                } else if (dndMode?.isActive == true) {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                        Icon.Resource(
+                            R.drawable.qs_dnd_icon_on,
+                            ContentDescription.Resource(R.string.dnd_is_on),
+                        ),
+                        ActivationState.Active,
+                    )
+                } else {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                        Icon.Resource(
+                            R.drawable.qs_dnd_icon_off,
+                            ContentDescription.Resource(R.string.dnd_is_off),
+                        ),
+                        ActivationState.Inactive,
+                    )
+                }
+            }
+        } else {
+            combine(
+                conflatedCallbackFlow {
+                    val callback =
+                        object : ZenModeController.Callback {
+                            override fun onZenChanged(zen: Int) {
+                                zenMode = zen
+                                trySendWithFailureLogging(updateState(), TAG)
+                            }
+
+                            override fun onZenAvailableChanged(available: Boolean) {
+                                oldIsAvailable = available
+                                trySendWithFailureLogging(updateState(), TAG)
+                            }
                         }
 
-                        override fun onZenAvailableChanged(available: Boolean) {
-                            isAvailable = available
-                            trySendWithFailureLogging(updateState(), TAG)
-                        }
-                    }
+                    zenMode = controller.zen
+                    oldIsAvailable = controller.isZenAvailable
+                    trySendWithFailureLogging(updateState(), TAG)
 
-                dndMode = controller.zen
-                isAvailable = controller.isZenAvailable
-                trySendWithFailureLogging(updateState(), TAG)
+                    controller.addCallback(callback)
 
-                controller.addCallback(callback)
-
-                awaitClose { controller.removeCallback(callback) }
-            },
-            secureSettings
-                .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
-                .onStart { emit(Unit) }
-                .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
-                .flowOn(backgroundDispatcher)
-                .distinctUntilChanged()
-                .onEach { settingsValue = it }
-        ) { callbackFlowValue, _ ->
-            callbackFlowValue
+                    awaitClose { controller.removeCallback(callback) }
+                },
+                secureSettings
+                    .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
+                    .onStart { emit(Unit) }
+                    .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
+                    .flowOn(backgroundDispatcher)
+                    .distinctUntilChanged()
+                    .onEach { settingsValue = it },
+            ) { callbackFlowValue, _ ->
+                callbackFlowValue
+            }
         }
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
-        return if (controller.isZenAvailable) {
+        val isZenAvailable = if (ModesUi.isEnabled) isAvailable.value else controller.isZenAvailable
+
+        return if (isZenAvailable) {
             KeyguardQuickAffordanceConfig.PickerScreenState.Default(
                 configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
             )
@@ -151,32 +217,63 @@
     override fun onTriggered(
         expandable: Expandable?
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
-        return when {
-            !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
-            dndMode != ZEN_MODE_OFF -> {
-                controller.setZen(ZEN_MODE_OFF, null, TAG)
+        return if (ModesUi.isEnabled) {
+            if (!isAvailable.value) {
                 KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+            } else {
+                val dnd = dndMode.value
+                if (dnd == null) {
+                    Log.wtf(TAG, "Triggered DND but it's null!?")
+                    return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                }
+                if (dnd.isActive) {
+                    interactor.deactivateMode(dnd)
+                    return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                } else {
+                    if (interactor.shouldAskForZenDuration(dnd)) {
+                        // NOTE: The dialog handles turning on the mode itself.
+                        return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+                            dialog.createDialog(),
+                            expandable,
+                        )
+                    } else {
+                        interactor.activateMode(dnd)
+                        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                    }
+                }
             }
-            settingsValue == ZEN_DURATION_PROMPT ->
-                KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
-                    dialog.createDialog(),
-                    expandable
-                )
-            settingsValue == ZEN_DURATION_FOREVER -> {
-                controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
-                KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
-            }
-            else -> {
-                controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
-                KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+        } else {
+            when {
+                !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                zenMode != ZEN_MODE_OFF -> {
+                    controller.setZen(ZEN_MODE_OFF, null, TAG)
+                    KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                }
+
+                settingsValue == ZEN_DURATION_PROMPT ->
+                    KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+                        dialog.createDialog(),
+                        expandable,
+                    )
+
+                settingsValue == ZEN_DURATION_FOREVER -> {
+                    controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
+                    KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                }
+
+                else -> {
+                    controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
+                    KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+                }
             }
         }
     }
 
     private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState {
-        return if (!isAvailable) {
+        ModesUi.assertInLegacyMode()
+        return if (!oldIsAvailable) {
             KeyguardQuickAffordanceConfig.LockScreenState.Hidden
-        } else if (dndMode == ZEN_MODE_OFF) {
+        } else if (zenMode == ZEN_MODE_OFF) {
             KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 Icon.Resource(
                     R.drawable.qs_dnd_icon_off,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 80a0cee..b0820a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -95,10 +94,7 @@
         scope.launch {
             powerInteractor.isAwake
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
-                .sample(
-                    keyguardInteractor.biometricUnlockState,
-                    ::Pair,
-                )
+                .sample(keyguardInteractor.biometricUnlockState, ::Pair)
                 .collect {
                     (
                         _,
@@ -203,21 +199,21 @@
                             if (!SceneContainerFlag.isEnabled) {
                                 startTransitionTo(
                                     KeyguardState.GONE,
-                                    ownerReason = "waking from dozing"
+                                    ownerReason = "waking from dozing",
                                 )
                             }
                         } else if (primaryBouncerShowing) {
                             if (!SceneContainerFlag.isEnabled) {
                                 startTransitionTo(
                                     KeyguardState.PRIMARY_BOUNCER,
-                                    ownerReason = "waking from dozing"
+                                    ownerReason = "waking from dozing",
                                 )
                             }
                         } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
                             if (!SceneContainerFlag.isEnabled) {
                                 startTransitionTo(
                                     KeyguardState.GLANCEABLE_HUB,
-                                    ownerReason = "waking from dozing"
+                                    ownerReason = "waking from dozing",
                                 )
                             }
                         } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
@@ -227,7 +223,7 @@
                         } else {
                             startTransitionTo(
                                 KeyguardState.LOCKSCREEN,
-                                ownerReason = "waking from dozing"
+                                ownerReason = "waking from dozing",
                             )
                         }
                     }
@@ -237,11 +233,9 @@
 
     private suspend fun transitionToGlanceableHub() {
         if (communalSceneKtfRefactor()) {
-            communalSceneInteractor.changeScene(
+            communalSceneInteractor.snapToScene(
                 newScene = CommunalScenes.Communal,
                 loggingReason = "from dozing to hub",
-                // Immediately show the hub when transitioning from dozing to hub.
-                transitionKey = CommunalTransitionKeys.Immediately,
             )
         } else {
             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 199caa1..7759298 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -162,10 +162,9 @@
                 .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
                 .collect {
                     if (communalSceneKtfRefactor()) {
-                        communalSceneInteractor.changeScene(
+                        communalSceneInteractor.snapToScene(
                             newScene = CommunalScenes.Blank,
                             loggingReason = "hub to dozing",
-                            transitionKey = CommunalTransitionKeys.Immediately,
                             keyguardState = KeyguardState.DOZING,
                         )
                     } else {
@@ -210,12 +209,12 @@
                             // ends, to avoid transitioning to OCCLUDED erroneously when exiting
                             // the dream.
                             .debounce(100.milliseconds),
-                        ::Pair
+                        ::Pair,
                     )
                     .sampleFilter(
                         // When launching activities from widgets on the hub, we have a
                         // custom occlusion animation.
-                        communalSceneInteractor.isLaunchingWidget,
+                        communalSceneInteractor.isLaunchingWidget
                     ) { launchingWidget ->
                         !launchingWidget
                     }
@@ -253,7 +252,7 @@
                         noneOf(
                             // When launching activities from widgets on the hub, we wait to change
                             // scenes until the activity launch is complete.
-                            communalSceneInteractor.isLaunchingWidget,
+                            communalSceneInteractor.isLaunchingWidget
                         ),
                     )
                     .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
@@ -270,7 +269,7 @@
                                 newScene = CommunalScenes.Blank,
                                 loggingReason = "hub to gone",
                                 transitionKey = CommunalTransitionKeys.SimpleFade,
-                                keyguardState = KeyguardState.GONE
+                                keyguardState = KeyguardState.GONE,
                             )
                         }
                     }
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 8505a784..49a8758 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
@@ -123,7 +123,7 @@
  * Class that is responsible for keeping the view carousel up to date. This also handles changes in
  * state and applies them to the media carousel like the expansion.
  */
-@ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class MediaCarouselController
 @Inject
@@ -252,13 +252,13 @@
         fun calculateAlpha(
             squishinessFraction: Float,
             startPosition: Float,
-            endPosition: Float
+            endPosition: Float,
         ): Float {
             val transformFraction =
                 MathUtils.constrain(
                     (squishinessFraction - startPosition) / (endPosition - startPosition),
                     0F,
-                    1F
+                    1F,
                 )
             return TRANSFORM_BEZIER.getInterpolation(transformFraction)
         }
@@ -354,7 +354,7 @@
                 this::closeGuts,
                 falsingManager,
                 this::logSmartspaceImpression,
-                logger
+                logger,
             )
         carouselLocale = context.resources.configuration.locales.get(0)
         isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
@@ -387,7 +387,7 @@
             object : MediaHostStatesManager.Callback {
                 override fun onHostStateChanged(
                     @MediaLocation location: Int,
-                    mediaHostState: MediaHostState
+                    mediaHostState: MediaHostState,
                 ) {
                     updateUserVisibility()
                     if (location == desiredLocation) {
@@ -412,7 +412,7 @@
         bgExecutor.execute {
             globalSettings.registerContentObserverSync(
                 Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
-                animationScaleObserver
+                animationScaleObserver,
             )
         }
     }
@@ -452,7 +452,7 @@
                     data: MediaData,
                     immediately: Boolean,
                     receivedSmartspaceCardLatency: Int,
-                    isSsReactivated: Boolean
+                    isSsReactivated: Boolean,
                 ) {
                     debugLogger.logMediaLoaded(key, data.active)
                     if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
@@ -468,7 +468,7 @@
                                         SSPACE_CARD_REPORTED__LOCKSCREEN,
                                         SSPACE_CARD_REPORTED__DREAM_OVERLAY,
                                     ),
-                                rank = MediaPlayerData.getMediaPlayerIndex(key)
+                                rank = MediaPlayerData.getMediaPlayerIndex(key),
                             )
                         }
                         if (
@@ -500,7 +500,7 @@
                                             SSPACE_CARD_REPORTED__DREAM_OVERLAY,
                                         ),
                                     rank = index,
-                                    receivedLatencyMillis = receivedSmartspaceCardLatency
+                                    receivedLatencyMillis = receivedSmartspaceCardLatency,
                                 )
                             }
                         }
@@ -533,7 +533,7 @@
                 override fun onSmartspaceMediaDataLoaded(
                     key: String,
                     data: SmartspaceMediaData,
-                    shouldPrioritize: Boolean
+                    shouldPrioritize: Boolean,
                 ) {
                     debugLogger.logRecommendationLoaded(key, data.isActive)
                     // Log the case where the hidden media carousel with the existed inactive resume
@@ -568,7 +568,7 @@
                                         receivedLatencyMillis =
                                             (systemClock.currentTimeMillis() -
                                                     data.headphoneConnectionTimeMillis)
-                                                .toInt()
+                                                .toInt(),
                                     )
                                 }
                             }
@@ -589,7 +589,7 @@
                                 receivedLatencyMillis =
                                     (systemClock.currentTimeMillis() -
                                             data.headphoneConnectionTimeMillis)
-                                        .toInt()
+                                        .toInt(),
                             )
                         }
                         if (
@@ -644,10 +644,7 @@
         mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
         settingsButton.setOnClickListener {
             logger.logCarouselSettings()
-            activityStarter.startActivity(
-                settingsIntent,
-                /* dismissShade= */ true,
-            )
+            activityStarter.startActivity(settingsIntent, /* dismissShade= */ true)
         }
     }
 
@@ -739,7 +736,7 @@
         val lp =
             LinearLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT
+                ViewGroup.LayoutParams.WRAP_CONTENT,
             )
         when (commonViewModel) {
             is MediaCommonViewModel.MediaControl -> {
@@ -785,7 +782,7 @@
         ) {
             mediaCarouselScrollHandler.scrollToPlayer(
                 mediaCarouselScrollHandler.visibleMediaIndex,
-                destIndex = 0
+                destIndex = 0,
             )
         }
         mediaCarouselScrollHandler.onPlayersChanged()
@@ -799,7 +796,7 @@
         mediaCarouselScrollHandler.onPlayersChanged()
         onAddOrUpdateVisibleToUserCard(
             position,
-            commonViewModel is MediaCommonViewModel.MediaControl
+            commonViewModel is MediaCommonViewModel.MediaControl,
         )
     }
 
@@ -856,7 +853,7 @@
                 mediaCarouselScrollHandler.qsExpanded,
                 mediaCarouselScrollHandler.visibleMediaIndex,
                 currentEndLocation,
-                isMediaCardUpdate
+                isMediaCardUpdate,
             )
         }
     }
@@ -893,7 +890,7 @@
             secureSettings.getBoolForUser(
                 Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                 true,
-                UserHandle.USER_CURRENT
+                UserHandle.USER_CURRENT,
             )
         }
     }
@@ -926,7 +923,7 @@
 
     private fun reorderAllPlayers(
         previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
-        key: String? = null
+        key: String? = null,
     ) {
         mediaContent.removeAllViews()
         for (mediaPlayer in MediaPlayerData.players()) {
@@ -960,7 +957,7 @@
                 TAG,
                 "Size of players list and number of views in carousel are out of sync. " +
                     "Players size is ${MediaPlayerData.players().size}. " +
-                    "View count is ${mediaContent.childCount}."
+                    "View count is ${mediaContent.childCount}.",
             )
         }
     }
@@ -970,7 +967,7 @@
         key: String,
         oldKey: String?,
         data: MediaData,
-        isSsReactivated: Boolean
+        isSsReactivated: Boolean,
     ): Boolean =
         traceSection("MediaCarouselController#addOrUpdatePlayer") {
             MediaPlayerData.moveIfExists(oldKey, key)
@@ -992,7 +989,7 @@
                 val lp =
                     LinearLayout.LayoutParams(
                         ViewGroup.LayoutParams.MATCH_PARENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
                     )
                 newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
                 newPlayer.bindPlayer(data, key)
@@ -1005,7 +1002,7 @@
                     newPlayer,
                     systemClock,
                     isSsReactivated,
-                    debugLogger
+                    debugLogger,
                 )
                 updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true)
                 // Media data added from a recommendation card should starts playing.
@@ -1025,7 +1022,7 @@
                     existingPlayer,
                     systemClock,
                     isSsReactivated,
-                    debugLogger
+                    debugLogger,
                 )
                 val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
                 // In case of recommendations hits.
@@ -1051,7 +1048,7 @@
     private fun addSmartspaceMediaRecommendations(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean
+        shouldPrioritize: Boolean,
     ) =
         traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
             if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
@@ -1090,7 +1087,7 @@
             val lp =
                 LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
                 )
             newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
             newRecs.bindRecommendation(data)
@@ -1117,7 +1114,7 @@
                     TAG,
                     "Size of players list and number of views in carousel are out of sync. " +
                         "Players size is ${MediaPlayerData.players().size}. " +
-                        "View count is ${mediaContent.childCount}."
+                        "View count is ${mediaContent.childCount}.",
                 )
             }
         }
@@ -1173,7 +1170,7 @@
                     addSmartspaceMediaRecommendations(
                         it.targetId,
                         it,
-                        MediaPlayerData.shouldPrioritizeSs
+                        MediaPlayerData.shouldPrioritizeSs,
                     )
                 }
             } else {
@@ -1185,7 +1182,7 @@
                     key = key,
                     oldKey = null,
                     data = data,
-                    isSsReactivated = isSsReactivated
+                    isSsReactivated = isSsReactivated,
                 )
             }
             if (recreateMedia) {
@@ -1248,7 +1245,7 @@
         @MediaLocation startLocation: Int,
         @MediaLocation endLocation: Int,
         progress: Float,
-        immediately: Boolean
+        immediately: Boolean,
     ) {
         if (
             startLocation != currentStartLocation ||
@@ -1286,7 +1283,7 @@
                     squishFraction,
                     (pageIndicator.translationY + pageIndicator.height) /
                         mediaCarousel.measuredHeight,
-                    1F
+                    1F,
                 )
         var alpha = 1.0f
         if (!endIsVisible || !startIsVisible) {
@@ -1354,7 +1351,7 @@
             currentCarouselHeight = height
             mediaCarouselScrollHandler.setCarouselBounds(
                 currentCarouselWidth,
-                currentCarouselHeight
+                currentCarouselHeight,
             )
             updatePageIndicatorLocation()
             updatePageIndicatorAlpha()
@@ -1386,13 +1383,13 @@
 
     private fun updateViewControllerToState(
         viewController: MediaViewController,
-        noAnimation: Boolean
+        noAnimation: Boolean,
     ) {
         viewController.setCurrentState(
             startLocation = currentStartLocation,
             endLocation = currentEndLocation,
             transitionProgress = currentTransitionProgress,
-            applyImmediately = noAnimation
+            applyImmediately = noAnimation,
         )
     }
 
@@ -1411,7 +1408,7 @@
         desiredHostState: MediaHostState?,
         animate: Boolean,
         duration: Long = 200,
-        startDelay: Long = 0
+        startDelay: Long = 0,
     ) =
         traceSection("MediaCarouselController#onDesiredLocationChanged") {
             desiredHostState?.let {
@@ -1435,7 +1432,7 @@
                         if (animate) {
                             mediaPlayer.mediaViewController.animatePendingStateChange(
                                 duration = duration,
-                                delay = startDelay
+                                delay = startDelay,
                             )
                         }
                         if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
@@ -1506,7 +1503,7 @@
             mediaCarouselViewModel.onCardVisibleToUser(
                 qsExpanded,
                 mediaCarouselScrollHandler.visibleMediaIndex,
-                currentEndLocation
+                currentEndLocation,
             )
             return
         }
@@ -1524,7 +1521,7 @@
                     800, // SMARTSPACE_CARD_SEEN
                     it.mSmartspaceId,
                     it.mUid,
-                    intArrayOf(it.surfaceForSmartspaceLogging)
+                    intArrayOf(it.surfaceForSmartspaceLogging),
                 )
                 it.mIsImpressed = true
             }
@@ -1559,7 +1556,7 @@
         interactedSubcardCardinality: Int = 0,
         rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
         receivedLatencyMillis: Int = 0,
-        isSwipeToDismiss: Boolean = false
+        isSwipeToDismiss: Boolean = false,
     ) {
         if (MediaPlayerData.players().size <= rank) {
             return
@@ -1600,7 +1597,7 @@
                 interactedSubcardCardinality,
                 receivedLatencyMillis,
                 null, // Media cards cannot have subcards.
-                null // Media cards don't have dimensions today.
+                null, // Media cards don't have dimensions today.
             )
 
             if (DEBUG) {
@@ -1613,7 +1610,7 @@
                         "uid: $uid " +
                         "interactedSubcardRank: $interactedSubcardRank " +
                         "interactedSubcardCardinality: $interactedSubcardCardinality " +
-                        "received_latency_millis: $receivedLatencyMillis"
+                        "received_latency_millis: $receivedLatencyMillis",
                 )
             }
         }
@@ -1633,7 +1630,7 @@
                     it.mUid,
                     intArrayOf(it.surfaceForSmartspaceLogging),
                     rank = index,
-                    isSwipeToDismiss = true
+                    isSwipeToDismiss = true,
                 )
                 // Reset card impressed state when swipe to dismissed
                 it.mIsImpressed = false
@@ -1692,7 +1689,7 @@
             active = true,
             resumeAction = null,
             instanceId = InstanceId.fakeInstanceId(-1),
-            appUid = -1
+            appUid = -1,
         )
 
     // Whether should prioritize Smartspace card.
@@ -1741,7 +1738,7 @@
         player: MediaControlPanel,
         clock: SystemClock,
         isSsReactivated: Boolean,
-        debugLogger: MediaCarouselControllerLogger? = null
+        debugLogger: MediaCarouselControllerLogger? = null,
     ) {
         val removedPlayer = removeMediaPlayer(key)
         if (removedPlayer != null && removedPlayer != player) {
@@ -1754,7 +1751,7 @@
                 data,
                 key,
                 clock.currentTimeMillis(),
-                isSsReactivated = isSsReactivated
+                isSsReactivated = isSsReactivated,
             )
         mediaData.put(key, sortKey)
         mediaPlayers.put(sortKey, player)
@@ -1768,7 +1765,7 @@
         shouldPrioritize: Boolean,
         clock: SystemClock,
         debugLogger: MediaCarouselControllerLogger? = null,
-        update: Boolean = false
+        update: Boolean = false,
     ) {
         shouldPrioritizeSs = shouldPrioritize
         val removedPlayer = removeMediaPlayer(key)
@@ -1782,7 +1779,7 @@
                 EMPTY.copy(active = data.isActive, isPlaying = false),
                 key,
                 clock.currentTimeMillis(),
-                isSsReactivated = true
+                isSsReactivated = true,
             )
         mediaData.put(key, sortKey)
         mediaPlayers.put(sortKey, player)
@@ -1793,7 +1790,7 @@
     fun moveIfExists(
         oldKey: String?,
         newKey: String,
-        debugLogger: MediaCarouselControllerLogger? = null
+        debugLogger: MediaCarouselControllerLogger? = null,
     ) {
         if (oldKey == null || oldKey == newKey) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index aa6c08e..45aad82 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -140,6 +140,7 @@
                     }
                 },
                 onLongClick = { tile.onLongClick(expandable) },
+                accessibilityUiState = uiState.accessibilityUiState,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 3f3ad13..4f47536 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -28,6 +28,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
 import android.os.Build;
@@ -987,6 +988,19 @@
 
     @Override
     public void onConfigChanged(Configuration newConfig) {
+        // If the shade window is not visible, the bounds will not update until it becomes visible.
+        // Touches that should invoke shade expansion but are not within those incorrect bounds
+        // (because the shape of the shade window remains portrait after flipping to landscape) will
+        // be dropped, causing the shade expansion to fail silently. Since the shade doesn't open,
+        // it doesn't become visible, and the bounds will never update. Therefore, we must detect
+        // the incorrect bounds here and force the update so that touches are routed correctly.
+        if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) {
+            Rect bounds = newConfig.windowConfiguration.getBounds();
+            if (mWindowRootView.getWidth() != bounds.width()) {
+                mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width());
+                updateRootViewBounds(bounds);
+            }
+        }
         final boolean newScreenRotationAllowed = mKeyguardStateController
                 .isKeyguardScreenRotationAllowed();
 
@@ -996,6 +1010,16 @@
         }
     }
 
+    private void updateRootViewBounds(Rect bounds) {
+        int originalMlpWidth = mLp.width;
+        int originalMlpHeight = mLp.height;
+        mLp.width = bounds.width();
+        mLp.height = bounds.height();
+        mWindowManager.updateViewLayout(mWindowRootView, mLp);
+        mLp.width = originalMlpWidth;
+        mLp.height = originalMlpHeight;
+    }
+
     /**
      * When keyguard will be dismissed but didn't start animation yet.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index e7a397b..1693e62 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -31,18 +31,13 @@
     ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
     fun logNewState(state: Any) {
-        buffer.log(
-            TAG,
-            DEBUG,
-            { str1 = state.toString() },
-            { "Applying new state: $str1" }
-        )
+        buffer.log(TAG, DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" })
     }
 
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
-        noinline printer: LogMessage.() -> String
+        noinline printer: LogMessage.() -> String,
     ) {
         buffer.log(TAG, logLevel, initializer, printer)
     }
@@ -52,7 +47,8 @@
             TAG,
             DEBUG,
             { bool1 = visible },
-            { "Updating visibility, should be visible : $bool1" })
+            { "Updating visibility, should be visible : $bool1" },
+        )
     }
 
     fun logIsExpanded(
@@ -65,7 +61,7 @@
         headsUpNotificationShowing: Boolean,
         scrimsVisibilityNotTransparent: Boolean,
         backgroundBlurRadius: Boolean,
-        launchingActivityFromNotification: Boolean
+        launchingActivityFromNotification: Boolean,
     ) {
         buffer.log(
             TAG,
@@ -82,11 +78,13 @@
                 long2 = if (backgroundBlurRadius) 1 else 0
                 double1 = if (launchingActivityFromNotification) 1.0 else 0.0
             },
-            { "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " +
+            {
+                "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " +
                     "isKeyguardShowingAndNotOccluded $bool2, panelVisible $bool3, " +
                     "keyguardFadingAway $bool4, bouncerShowing $int1," +
                     "headsUpNotificationShowing $int2, scrimsVisibilityNotTransparent $long1," +
-                    "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"}
+                    "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"
+            },
         )
     }
 
@@ -95,7 +93,7 @@
             TAG,
             DEBUG,
             { bool1 = visible },
-            { "Updating shade, should be visible and focusable: $bool1" }
+            { "Updating shade, should be visible and focusable: $bool1" },
         )
     }
 
@@ -104,7 +102,19 @@
             TAG,
             DEBUG,
             { bool1 = focusable },
-            { "Updating shade, should be focusable : $bool1" }
+            { "Updating shade, should be focusable : $bool1" },
+        )
+    }
+
+    fun logConfigChangeWidthAdjust(originalWidth: Int, newWidth: Int) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = originalWidth
+                int2 = newWidth
+            },
+            { "Config changed. SceneWindowRootView width updating from $int1 to $int2." },
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 4056e7b..975b92e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -16,11 +16,12 @@
 package com.android.systemui.statusbar.core
 
 import android.app.Fragment
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
-import com.android.systemui.statusbar.phone.PhoneStatusBarView
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
@@ -33,31 +34,60 @@
  * Responsible for creating the status bar window and initializing the root components of that
  * window (see [CollapsedStatusBarFragment])
  */
-@SysUISingleton
-class StatusBarInitializer @Inject constructor(
-    private val windowController: StatusBarWindowController,
-    private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
-    private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
-) {
+interface StatusBarInitializer {
 
-    var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+    var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener?
 
     /**
      * Creates the status bar window and root views, and initializes the component.
      *
      * TODO(b/277764509): Initialize the status bar via [CoreStartable#start].
      */
-    fun initializeStatusBar() {
-        windowController.fragmentHostManager.addTagListener(
+    fun initializeStatusBar()
+
+    interface OnStatusBarViewInitializedListener {
+
+        /**
+         * The status bar view has been initialized.
+         *
+         * @param component Dagger component that is created when the status bar view is created.
+         *   Can be used to retrieve dependencies from that scope, including the status bar root
+         *   view.
+         */
+        fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
+    }
+
+    interface OnStatusBarViewUpdatedListener {
+        fun onStatusBarViewUpdated(
+            statusBarViewController: PhoneStatusBarViewController,
+            statusBarTransitions: PhoneStatusBarTransitions,
+        )
+    }
+}
+
+@SysUISingleton
+class StatusBarInitializerImpl
+@Inject
+constructor(
+    private val windowController: StatusBarWindowController,
+    private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
+    private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
+) : StatusBarInitializer {
+
+    override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+
+    override fun initializeStatusBar() {
+        windowController.fragmentHostManager
+            .addTagListener(
                 CollapsedStatusBarFragment.TAG,
                 object : FragmentHostManager.FragmentListener {
                     override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
-                        val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
-                                .statusBarFragmentComponent ?: throw IllegalStateException()
+                        val statusBarFragmentComponent =
+                            (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent
+                                ?: throw IllegalStateException()
                         statusBarViewUpdatedListener?.onStatusBarViewUpdated(
-                            statusBarFragmentComponent.phoneStatusBarView,
                             statusBarFragmentComponent.phoneStatusBarViewController,
-                            statusBarFragmentComponent.phoneStatusBarTransitions
+                            statusBarFragmentComponent.phoneStatusBarTransitions,
                         )
                         creationListeners.forEach { listener ->
                             listener.onStatusBarViewInitialized(statusBarFragmentComponent)
@@ -67,33 +97,15 @@
                     override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
                         // nop
                     }
-                }
-        ).fragmentManager
-                .beginTransaction()
-                .replace(
-                    R.id.status_bar_container,
-                    collapsedStatusBarFragmentProvider.get(),
-                    CollapsedStatusBarFragment.TAG
-                )
-                .commit()
-    }
-
-    interface OnStatusBarViewInitializedListener {
-
-        /**
-         * The status bar view has been initialized.
-         *
-         * @param component Dagger component that is created when the status bar view is created.
-         * Can be used to retrieve dependencies from that scope, including the status bar root view.
-         */
-        fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
-    }
-
-    interface OnStatusBarViewUpdatedListener {
-        fun onStatusBarViewUpdated(
-            statusBarView: PhoneStatusBarView,
-            statusBarViewController: PhoneStatusBarViewController,
-            statusBarTransitions: PhoneStatusBarTransitions
-        )
+                },
+            )
+            .fragmentManager
+            .beginTransaction()
+            .replace(
+                R.id.status_bar_container,
+                collapsedStatusBarFragmentProvider.get(),
+                CollapsedStatusBarFragment.TAG,
+            )
+            .commit()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
new file mode 100644
index 0000000..2141513
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.statusbar.core
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading and using the status bar simple fragment flag state */
+object StatusBarSimpleFragment {
+    /** Aconfig flag for removing the fragment */
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.statusBarSimpleFragment()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 406a664..526c64c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -20,12 +20,16 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -57,6 +61,13 @@
     @ClassKey(StatusBarSignalPolicy::class)
     abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
 
+    @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
+    @Binds
+    abstract fun statusBarWindowController(
+        impl: StatusBarWindowControllerImpl
+    ): StatusBarWindowController
+
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
index c0302bc..9af4b8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -25,6 +25,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 
 /**
  * Repository used for tracking the state of notification remote input (e.g. when the user presses
@@ -33,14 +34,21 @@
 interface RemoteInputRepository {
     /** Whether remote input is currently active for any notification. */
     val isRemoteInputActive: Flow<Boolean>
+
+    /**
+     * The bottom bound of the currently focused remote input notification row, or null if there
+     * isn't one.
+     */
+    val remoteInputRowBottomBound: Flow<Float?>
+
+    fun setRemoteInputRowBottomBound(bottom: Float?)
 }
 
 @SysUISingleton
 class RemoteInputRepositoryImpl
 @Inject
-constructor(
-    private val notificationRemoteInputManager: NotificationRemoteInputManager,
-) : RemoteInputRepository {
+constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) :
+    RemoteInputRepository {
     override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
         trySend(false) // initial value is false
         val callback =
@@ -52,6 +60,12 @@
         notificationRemoteInputManager.addControllerCallback(callback)
         awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
     }
+
+    override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null)
+
+    override fun setRemoteInputRowBottomBound(bottom: Float?) {
+        remoteInputRowBottomBound.value = bottom
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
index 68f727b..b83b0cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -20,13 +20,24 @@
 import com.android.systemui.statusbar.data.repository.RemoteInputRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
 
 /**
  * Interactor used for business logic pertaining to the notification remote input (e.g. when the
  * user presses "reply" on a notification and the keyboard opens).
  */
 @SysUISingleton
-class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+class RemoteInputInteractor
+@Inject
+constructor(private val remoteInputRepository: RemoteInputRepository) {
     /** Is remote input currently active for a notification? */
     val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+
+    /** The bottom bound of the currently focused remote input notification row. */
+    val remoteInputRowBottomBound: Flow<Float> =
+        remoteInputRepository.remoteInputRowBottomBound.mapNotNull { it }
+
+    fun setRemoteInputRowBottomBound(bottom: Float?) {
+        remoteInputRepository.setRemoteInputRowBottomBound(bottom)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index cb3e26b..5003a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -21,6 +21,7 @@
 
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE;
 import static com.android.systemui.util.ColorUtilKt.hexColorString;
 
 import android.animation.Animator;
@@ -83,6 +84,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -118,6 +120,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
+import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
 import com.android.systemui.util.Compile;
@@ -830,6 +833,20 @@
         mPrivateLayout.setRemoteInputController(r);
     }
 
+    /**
+     * Return the cumulative y-value that the actions container expands via its scale animator when
+     * remote input is activated.
+     */
+    public float getRemoteInputActionsContainerExpandedOffset() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput();
+        if (expandedRemoteInput == null) return 0f;
+        View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout();
+        if (actionsContainerLayout == null) return 0f;
+
+        return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f;
+    }
+
     public void addChildNotification(ExpandableNotificationRow row) {
         addChildNotification(row, -1);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7543f3b..e7c67f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -99,6 +99,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -120,7 +121,6 @@
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -740,6 +740,15 @@
         updateFooter();
     }
 
+    void sendRemoteInputRowBottomBound(Float bottom) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        if (bottom != null) {
+            bottom += getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.notification_content_margin);
+        }
+        mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
+    }
+
     /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
     public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
         FooterViewRefactor.assertInLegacyMode();
@@ -1274,6 +1283,11 @@
     }
 
     @Override
+    public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) {
+        mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer);
+    }
+
+    @Override
     public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
         mScrollViewFields.setHeadsUpHeightConsumer(consumer);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e5f63c1..dad6894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -98,6 +98,9 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -129,9 +132,6 @@
 import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -1605,6 +1605,9 @@
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
                     boolean remoteInputActive) {
+                if (SceneContainerFlag.isEnabled()) {
+                    sendRemoteInputRowBottomBound(entry, remoteInputActive);
+                }
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
                 if (!FooterViewRefactor.isEnabled()) {
@@ -1620,6 +1623,15 @@
                 mView.requestDisallowLongPress();
                 mView.requestDisallowDismiss();
             }
+
+            private void sendRemoteInputRowBottomBound(NotificationEntry entry,
+                    boolean remoteInputActive) {
+                ExpandableNotificationRow row = entry.getRow();
+                float top = row.getTranslationY();
+                int height = row.getActualHeight();
+                float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset();
+                mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null);
+            }
         };
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index aa39539..c08ed61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -57,6 +57,13 @@
      * guts off of this gesture, we can notify the placeholder through here.
      */
     var currentGestureInGutsConsumer: Consumer<Boolean>? = null
+
+    /**
+     * When a notification begins remote input, its bottom Y bound is sent to the placeholder
+     * through here in order to adjust to accommodate the IME.
+     */
+    var remoteInputRowBottomBoundConsumer: Consumer<Float?>? = null
+
     /**
      * Any time the heads up height is recalculated, it should be updated here to be used by the
      * placeholder
@@ -75,6 +82,10 @@
     fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) =
         currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts)
 
+    /** send [bottomY] to the [remoteInputRowBottomBoundConsumer], if present. */
+    fun sendRemoteInputRowBottomBound(bottomY: Float?) =
+        remoteInputRowBottomBoundConsumer?.accept(bottomY)
+
     /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
     fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 235b4da..41c0293 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -74,6 +74,9 @@
     /** Set a consumer for current gesture in guts events */
     fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?)
 
+    /** Set a consumer for current remote input notification row bottom bound events */
+    fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?)
+
     /** Set a consumer for heads up height changed events */
     fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 6d5553f..2e37dea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -108,10 +108,14 @@
                 view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
                 view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
                 view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer)
+                view.setRemoteInputRowBottomBoundConsumer(
+                    viewModel.remoteInputRowBottomBoundConsumer
+                )
                 DisposableHandle {
                     view.setSyntheticScrollConsumer(null)
                     view.setCurrentGestureOverscrollConsumer(null)
                     view.setCurrentGestureInGutsConsumer(null)
+                    view.setRemoteInputRowBottomBoundConsumer(null)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 8d7007b..5b2e02d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
@@ -56,6 +57,7 @@
     dumpManager: DumpManager,
     stackAppearanceInteractor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
+    private val remoteInputInteractor: RemoteInputInteractor,
     private val sceneInteractor: SceneInteractor,
     // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
     // while the flag is off, creating this object too early results in a crash
@@ -240,6 +242,10 @@
     val currentGestureInGutsConsumer: (Boolean) -> Unit =
         stackAppearanceInteractor::setCurrentGestureInGuts
 
+    /** Receives the bottom bound of the currently focused remote input notification row. */
+    val remoteInputRowBottomBoundConsumer: (Float?) -> Unit =
+        remoteInputInteractor::setRemoteInputRowBottomBound
+
     /** Whether the notification stack is scrollable or not. */
     val isScrollable: Flow<Boolean> =
         combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 69c1bf3..c8e8358 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -49,6 +50,7 @@
     private val sceneInteractor: SceneInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+    remoteInputInteractor: RemoteInputInteractor,
     featureFlags: FeatureFlagsClassic,
     dumpManager: DumpManager,
 ) :
@@ -132,6 +134,12 @@
     val isCurrentGestureOverscroll: Flow<Boolean> =
         interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
 
+    /** Whether remote input is currently active for any notification. */
+    val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive
+
+    /** The bottom bound of the currently focused remote input notification row. */
+    val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound
+
     /** Sets whether the notification stack is scrolled to the top. */
     fun setScrolledToTop(scrolledToTop: Boolean) {
         interactor.setScrolledToTop(scrolledToTop)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 50e9249..59533b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -372,7 +372,6 @@
 
     private final Point mCurrentDisplaySize = new Point();
 
-    protected PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
     private final AuthRippleController mAuthRippleController;
@@ -1191,8 +1190,7 @@
 
         // Set up CollapsedStatusBarFragment and PhoneStatusBarView
         mStatusBarInitializer.setStatusBarViewUpdatedListener(
-                (statusBarView, statusBarViewController, statusBarTransitions) -> {
-                    mStatusBarView = statusBarView;
+                (statusBarViewController, statusBarTransitions) -> {
                     mPhoneStatusBarViewController = statusBarViewController;
                     mStatusBarTransitions = statusBarTransitions;
                     getNotificationShadeWindowViewController()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index c0e36b2..f026b99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -27,7 +27,6 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
@@ -114,14 +113,6 @@
     /** */
     @Provides
     @StatusBarFragmentScope
-    static StatusBarUserSwitcherContainer provideStatusBarUserSwitcherContainer(
-            @RootView PhoneStatusBarView view) {
-        return view.findViewById(R.id.user_switcher_container);
-    }
-
-    /** */
-    @Provides
-    @StatusBarFragmentScope
     static PhoneStatusBarViewController providePhoneStatusBarViewController(
             PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
             @RootView PhoneStatusBarView phoneStatusBarView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 31776cf..16d5f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -106,7 +106,7 @@
     private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
     private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
     private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
-    private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+    public static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
     private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120;
     private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index dbeaa59..ba45942 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -27,7 +27,10 @@
 import com.android.settingslib.notification.modes.ZenIconLoader
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
 import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
 import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
 import java.time.Duration
@@ -51,7 +54,17 @@
     private val notificationSettingsRepository: NotificationSettingsRepository,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val iconLoader: ZenIconLoader,
+    private val deviceProvisioningRepository: DeviceProvisioningRepository,
+    private val userSetupRepository: UserSetupRepository,
 ) {
+    val isZenAvailable: Flow<Boolean> =
+        combine(
+            deviceProvisioningRepository.isDeviceProvisioned,
+            userSetupRepository.isUserSetUp,
+        ) { isDeviceProvisioned, isUserSetUp ->
+            isDeviceProvisioned && isUserSetUp
+        }
+
     val isZenModeEnabled: Flow<Boolean> =
         zenModeRepository.globalZenMode
             .map {
@@ -80,6 +93,18 @@
 
     val modes: Flow<List<ZenMode>> = zenModeRepository.modes
 
+    /**
+     * Returns the special "manual DND" mode.
+     *
+     * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
+     * specifically; any new or migrated features should use modes more generally, through [modes]
+     * or [activeModes].
+     */
+    val dndMode: Flow<ZenMode?> by lazy {
+        ModesUi.assertInNewMode()
+        zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+    }
+
     /** Flow returning the currently active mode(s), if any. */
     val activeModes: Flow<ActiveZenModes> =
         modes
@@ -113,10 +138,11 @@
                         Log.e(
                             TAG,
                             "Interactor cannot handle showing the zen duration prompt. " +
-                                "Please use EnableZenModeDialog when this setting is active."
+                                "Please use EnableZenModeDialog when this setting is active.",
                         )
                         null
                     }
+
                     ZEN_DURATION_FOREVER -> null
                     else -> Duration.ofMinutes(zenDuration.toLong())
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
new file mode 100644
index 0000000..421e5c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+/** Encapsulates all logic for the status bar window state management. */
+interface StatusBarWindowController {
+    val statusBarHeight: Int
+
+    /** Rereads the status bar height and reapplies the current state if the height is different. */
+    fun refreshStatusBarHeight()
+
+    /** Adds the status bar view to the window manager. */
+    fun attach()
+
+    /** Adds the given view to the status bar window view. */
+    fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
+
+    /** Returns the status bar window's background view. */
+    val backgroundView: View
+
+    /** Returns a fragment host manager for the status bar window view. */
+    val fragmentHostManager: FragmentHostManager
+
+    /**
+     * Provides an updated animation controller if we're animating a view in the status bar.
+     *
+     * This is needed because we have to make sure that the status bar window matches the full
+     * screen during the animation and that we are expanding the view below the other status bar
+     * text.
+     *
+     * @param rootView the root view of the animation
+     * @param animationController the default animation controller to use
+     * @return If the animation is on a view in the status bar, returns an Optional containing an
+     *   updated animation controller that handles status-bar-related animation details. Returns an
+     *   empty optional if the animation is *not* on a view in the status bar.
+     */
+    fun wrapAnimationControllerIfInStatusBar(
+        rootView: View,
+        animationController: ActivityTransitionAnimator.Controller,
+    ): Optional<ActivityTransitionAnimator.Controller>
+
+    /** Set force status bar visible. */
+    fun setForceStatusBarVisible(forceStatusBarVisible: Boolean)
+
+    /**
+     * Sets whether an ongoing process requires the status bar to be forced visible.
+     *
+     * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+     * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+     * false but this method is set to true, then the status bar **will** be visible.
+     *
+     * TODO(b/195839150): We should likely merge this method and {@link
+     *   this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+     */
+    fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index c30a6b7..1a0327c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -46,6 +46,8 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -67,7 +69,7 @@
  * Encapsulates all logic for the status bar window state management.
  */
 @SysUISingleton
-public class StatusBarWindowController {
+public class StatusBarWindowControllerImpl implements StatusBarWindowController {
     private static final String TAG = "StatusBarWindowController";
     private static final boolean DEBUG = false;
 
@@ -89,7 +91,7 @@
     private final Binder mInsetsSourceOwner = new Binder();
 
     @Inject
-    public StatusBarWindowController(
+    public StatusBarWindowControllerImpl(
             Context context,
             @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
             ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
@@ -117,14 +119,12 @@
                                 /* attachedViewProvider=*/ () -> mStatusBarWindowView)));
     }
 
+    @Override
     public int getStatusBarHeight() {
         return mBarHeight;
     }
 
-    /**
-     * Rereads the status bar height and reapplys the current state if the height
-     * is different.
-     */
+    @Override
     public void refreshStatusBarHeight() {
         Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight");
         try {
@@ -141,9 +141,7 @@
         }
     }
 
-    /**
-     * Adds the status bar view to the window manager.
-     */
+    @Override
     public void attach() {
         // Now that the status bar window encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -161,54 +159,47 @@
         apply(mCurrentState);
     }
 
-    /** Adds the given view to the status bar window view. */
-    public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) {
+    @Override
+    public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
         mStatusBarWindowView.addView(view, layoutParams);
     }
 
-    /** Returns the status bar window's background view. */
+    @NonNull
+    @Override
     public View getBackgroundView() {
         return mStatusBarWindowView.findViewById(R.id.status_bar_container);
     }
 
-    /** Returns a fragment host manager for the status bar window view. */
+    @NonNull
+    @Override
     public FragmentHostManager getFragmentHostManager() {
         return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
     }
 
-    /**
-     * Provides an updated animation controller if we're animating a view in the status bar.
-     *
-     * This is needed because we have to make sure that the status bar window matches the full
-     * screen during the animation and that we are expanding the view below the other status bar
-     * text.
-     *
-     * @param rootView the root view of the animation
-     * @param animationController the default animation controller to use
-     * @return If the animation is on a view in the status bar, returns an Optional containing an
-     *   updated animation controller that handles status-bar-related animation details. Returns an
-     *   empty optional if the animation is *not* on a view in the status bar.
-     */
+    @NonNull
+    @Override
     public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar(
-            View rootView, ActivityTransitionAnimator.Controller animationController) {
+            @NonNull View rootView,
+            @NonNull ActivityTransitionAnimator.Controller animationController) {
         if (rootView != mStatusBarWindowView) {
             return Optional.empty();
         }
 
         animationController.setTransitionContainer(mLaunchAnimationContainer);
-        return Optional.of(new DelegateTransitionAnimatorController(animationController) {
-            @Override
-            public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
-                getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
-                setLaunchAnimationRunning(true);
-            }
+        return Optional.of(
+                new DelegateTransitionAnimatorController(animationController) {
+                    @Override
+                    public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+                        getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
+                        setLaunchAnimationRunning(true);
+                    }
 
-            @Override
-            public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
-                getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
-                setLaunchAnimationRunning(false);
-            }
-        });
+                    @Override
+                    public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+                        getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
+                        setLaunchAnimationRunning(false);
+                    }
+                });
     }
 
     private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
@@ -275,22 +266,13 @@
         }
     }
 
-    /** Set force status bar visible. */
+    @Override
     public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
         mCurrentState.mForceStatusBarVisible = forceStatusBarVisible;
         apply(mCurrentState);
     }
 
-    /**
-     * Sets whether an ongoing process requires the status bar to be forced visible.
-     *
-     * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
-     * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
-     * false but this method is set to true, then the status bar **will** be visible.
-     *
-     * TODO(b/195839150): We should likely merge this method and
-     * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
-     */
+    @Override
     public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
         mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
         apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
index 1a41987..80ea925 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
@@ -30,12 +30,12 @@
     private val logger: InputDeviceTutorialLogger,
 ) {
     fun disableGestures() {
-        logger.log("Disabling touchpad gestures across the system")
+        logger.d("Disabling touchpad gestures across the system")
         setGesturesState(disabled = true)
     }
 
     fun enableGestures() {
-        logger.log("Enabling touchpad gestures across the system")
+        logger.d("Enabling touchpad gestures across the system")
         setGesturesState(disabled = false)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 6fe6547..d03b2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -57,6 +57,7 @@
         }
         // required to handle 3+ fingers on touchpad
         window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL)
     }
 
     private fun finishTutorial() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index db4f9ef..7166428 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -35,7 +35,6 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
 import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
-import static com.android.systemui.Flags.hapticVolumeSlider;
 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
@@ -928,10 +927,8 @@
     }
 
     private void addSliderHapticsToRow(VolumeRow row) {
-        if (hapticVolumeSlider()) {
-            row.createPlugin(mVibratorHelper, mSystemClock);
-            HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
-        }
+        row.createPlugin(mVibratorHelper, mSystemClock);
+        HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
     }
 
     @VisibleForTesting void addSliderHapticsToRows() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 103449b..ee8ce17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.UiModeManager;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.platform.test.annotations.EnableFlags;
@@ -78,6 +79,12 @@
         mNightMode = mUiModeManager.getNightMode();
         mUiModeManager.setNightMode(MODE_NIGHT_YES);
 
+        // Programmatically update the resource's configuration to night mode to reduce flakiness
+        Configuration nightConfig = new Configuration(mContext.getResources().getConfiguration());
+        nightConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
+        mContext.getResources().updateConfiguration(nightConfig,
+                mContext.getResources().getDisplayMetrics(), null);
+
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
 
@@ -101,6 +108,8 @@
 
     @Test
     public void insetsOnDarkTheme_menuOnLeft_matchInsets() {
+        // In dark theme, the inset is not 0 to avoid weird spacing issue between the menu and
+        // the edge of the screen.
         mMenuView.onConfigurationChanged(/* newConfig= */ null);
         final InstantInsetLayerDrawable insetLayerDrawable =
                 (InstantInsetLayerDrawable) mMenuView.getBackground();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 4b61a0d..088bb02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.filters.LargeTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.testKosmos
@@ -55,6 +56,7 @@
         kosmos.patternBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
             onIntentionalUserInput = {},
+            bouncerHapticPlayer = kosmos.bouncerHapticPlayer,
         )
 
     @Before
@@ -75,11 +77,11 @@
                     content = { play -> if (play) PatternBouncerUnderTest() },
                     ComposeRecordingSpec.until(
                         recordBefore = false,
-                        checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }
+                        checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) },
                     ) {
                         feature(MotionTestKeys.dotAppearFadeIn, floatArray)
                         feature(MotionTestKeys.dotAppearMoveUp, floatArray)
-                    }
+                    },
                 )
 
             assertThat(motion).timeSeriesMatchesGolden()
@@ -100,7 +102,7 @@
                         viewModel.onDragEnd()
                         // Failure animation starts when animateFailure flips to true...
                         viewModel.animateFailure.takeWhile { !it }.collect {}
-                    }
+                    },
                 ) {
                     // ... and ends when the composable flips it back to false.
                     viewModel.animateFailure.takeWhile { it }.collect {}
@@ -111,7 +113,7 @@
                     content = { PatternBouncerUnderTest() },
                     ComposeRecordingSpec(failureAnimationMotionControl) {
                         feature(MotionTestKeys.dotScaling, floatArray)
-                    }
+                    },
                 )
             assertThat(motion).timeSeriesMatchesGolden()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index c65a117..d72b72c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -32,6 +32,7 @@
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
@@ -101,8 +102,18 @@
         when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
-        mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
-                mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
+        mClipboardListener = new ClipboardListener(
+                getContext(),
+                mOverlayControllerProvider,
+                mClipboardToast,
+                user -> {
+                    if (UserHandle.CURRENT.equals(user)) {
+                        return mClipboardManager;
+                    }
+                    return null;
+                },
+                mKeyguardManager,
+                mUiEventLogger);
     }
 
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index af04309..c710c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -168,7 +168,7 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -504,7 +504,7 @@
                 mock(FragmentService.class),
                 mLightBarController,
                 mAutoHideController,
-                new StatusBarInitializer(
+                new StatusBarInitializerImpl(
                         mStatusBarWindowController,
                         mCollapsedStatusBarFragmentProvider,
                         emptySet()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 1e2648b22..ecc7909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,7 +20,6 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 
-import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
@@ -51,7 +50,6 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
@@ -285,23 +283,8 @@
     }
 
     @Test
-    @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
-    public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() {
-        // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows
-        mDialog.addSliderHapticsToRows();
-
-        // WHEN haptics try to be delivered to a volume stream
-        boolean canDeliverHaptics =
-                mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
-
-        // THEN the result is that haptics are not successfully delivered
-        assertFalse(canDeliverHaptics);
-    }
-
-    @Test
-    @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
-    public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() {
-        // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows
+    public void addSliderHaptics_canDeliverOnProgressChangedHaptics() {
+        // GIVEN that the slider haptics are added to rows
         mDialog.addSliderHapticsToRows();
 
         // WHEN haptics try to be delivered to a volume stream
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 649e4e8..1b1d8c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -34,9 +36,7 @@
 import kotlinx.coroutines.flow.StateFlow
 
 val Kosmos.bouncerUserActionsViewModel by Fixture {
-    BouncerUserActionsViewModel(
-        bouncerInteractor = bouncerInteractor,
-    )
+    BouncerUserActionsViewModel(bouncerInteractor = bouncerInteractor)
 }
 
 val Kosmos.bouncerUserActionsViewModelFactory by Fixture {
@@ -59,6 +59,7 @@
         pinViewModelFactory = pinBouncerViewModelFactory,
         patternViewModelFactory = patternBouncerViewModelFactory,
         passwordViewModelFactory = passwordBouncerViewModelFactory,
+        bouncerHapticPlayer = bouncerHapticPlayer,
     )
 }
 
@@ -76,6 +77,7 @@
             isInputEnabled: StateFlow<Boolean>,
             onIntentionalUserInput: () -> Unit,
             authenticationMethod: AuthenticationMethodModel,
+            bouncerHapticPlayer: BouncerHapticPlayer,
         ): PinBouncerViewModel {
             return PinBouncerViewModel(
                 applicationContext = applicationContext,
@@ -84,6 +86,7 @@
                 isInputEnabled = isInputEnabled,
                 onIntentionalUserInput = onIntentionalUserInput,
                 authenticationMethod = authenticationMethod,
+                bouncerHapticPlayer = bouncerHapticPlayer,
             )
         }
     }
@@ -92,6 +95,7 @@
 val Kosmos.patternBouncerViewModelFactory by Fixture {
     object : PatternBouncerViewModel.Factory {
         override fun create(
+            bouncerHapticPlayer: BouncerHapticPlayer,
             isInputEnabled: StateFlow<Boolean>,
             onIntentionalUserInput: () -> Unit,
         ): PatternBouncerViewModel {
@@ -100,6 +104,7 @@
                 interactor = bouncerInteractor,
                 isInputEnabled = isInputEnabled,
                 onIntentionalUserInput = onIntentionalUserInput,
+                bouncerHapticPlayer = bouncerHapticPlayer,
             )
         }
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 1ed10fbe..8922b2f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
 import com.android.systemui.flags.fakeSystemPropertiesHelper
 import com.android.systemui.flags.systemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.trustInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -37,5 +38,6 @@
         powerInteractor = powerInteractor,
         biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
         systemPropertiesHelper = fakeSystemPropertiesHelper,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
index 5ad973a..2b81da3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -20,8 +20,10 @@
 import com.google.android.msdl.data.model.MSDLToken
 import com.google.android.msdl.domain.InteractionProperties
 import com.google.android.msdl.domain.MSDLPlayer
+import com.google.android.msdl.logging.MSDLEvent
 
 class FakeMSDLPlayer : MSDLPlayer {
+    private val history = arrayListOf<MSDLEvent>()
     var currentFeedbackLevel = FeedbackLevel.DEFAULT
     var latestTokenPlayed: MSDLToken? = null
         private set
@@ -34,5 +36,8 @@
     override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
         latestTokenPlayed = token
         latestPropertiesPlayed = properties
+        history.add(MSDLEvent(token, properties))
     }
+
+    override fun getHistory(): List<MSDLEvent> = history
 }
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 ca748b66..80db1e9 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,6 +16,7 @@
 
 package com.android.systemui.haptics.qs
 
+import com.android.systemui.classifier.fakeFalsingManager
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.log.core.FakeLogBuffer
@@ -26,6 +27,7 @@
         QSLongPressEffect(
             vibratorHelper,
             keyguardStateController,
+            fakeFalsingManager,
             FakeLogBuffer.Factory.create(),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index c416ea1..91602c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -16,8 +16,13 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 
 class FakeRemoteInputRepository : RemoteInputRepository {
     override val isRemoteInputActive = MutableStateFlow(false)
+    override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null)
+
+    override fun setRemoteInputRowBottomBound(bottom: Float?) {}
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 6370a5d..7244d46 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
 val Kosmos.notificationScrollViewModel by Fixture {
@@ -29,6 +30,7 @@
         dumpManager = dumpManager,
         stackAppearanceInteractor = notificationStackAppearanceInteractor,
         shadeInteractor = shadeInteractor,
+        remoteInputInteractor = remoteInputInteractor,
         sceneInteractor = sceneInteractor,
         keyguardInteractor = { keyguardInteractor },
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 8bfc390..e5cf0a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
@@ -31,6 +32,7 @@
         sceneInteractor = sceneInteractor,
         shadeInteractor = shadeInteractor,
         headsUpNotificationInteractor = headsUpNotificationInteractor,
+        remoteInputInteractor = remoteInputInteractor,
         featureFlags = featureFlagsClassic,
         dumpManager = dumpManager,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 61b53c9..99cd830 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 
 val Kosmos.zenModeInteractor by Fixture {
@@ -31,5 +33,7 @@
         notificationSettingsRepository = notificationSettingsRepository,
         bgDispatcher = testDispatcher,
         iconLoader = zenIconLoader,
+        deviceProvisioningRepository = deviceProvisioningRepository,
+        userSetupRepository = userSetupRepository,
     )
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index a100974..57b58d8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -128,7 +128,11 @@
 
         val currentDirection =
             if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
-        if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
+        val changedDirectionWhileInTransition =
+            isTransitionInProgress && currentDirection != lastFoldUpdate
+        val unfoldedPastThresholdSinceLastTransition =
+            angle - lastHingeAngleBeforeTransition > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
+        if (changedDirectionWhileInTransition || unfoldedPastThresholdSinceLastTransition) {
             lastHingeAngleBeforeTransition = lastHingeAngle
         }
 
@@ -153,7 +157,7 @@
                 isOnLargeScreen // Avoids sending closing event when on small screen.
         // Start event is sent regardless due to hall sensor.
         ) {
-            notifyFoldUpdate(transitionUpdate, lastHingeAngle)
+            notifyFoldUpdate(transitionUpdate, angle)
         }
 
         if (isTransitionInProgress) {
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 4cb2ce1..5d251bd 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -195,23 +195,25 @@
         try {
             performGlobalInitialization();
 
+            /*
+             * If the class has @DisabledOnRavenwood, then we'll delegate to
+             * ClassSkippingTestRunner, which simply skips it.
+             *
+             * We need to do it before instantiating TestClass for b/367694651.
+             */
+            if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
+                    testClass)) {
+                mRealRunner = new ClassSkippingTestRunner(testClass);
+                mDescription = mRealRunner.getDescription();
+                return;
+            }
+
             mTestClass = new TestClass(testClass);
 
             Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
 
             onRunnerInitializing();
 
-            /*
-             * If the class has @DisabledOnRavenwood, then we'll delegate to
-             * ClassSkippingTestRunner, which simply skips it.
-             */
-            if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
-                    mTestClass.getJavaClass())) {
-                mRealRunner = new ClassSkippingTestRunner(mTestClass);
-                mDescription = mRealRunner.getDescription();
-                return;
-            }
-
             // Find the real runner.
             final Class<? extends Runner> realRunnerClass;
             final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class);
@@ -444,14 +446,11 @@
      * filter.
      */
     private static class ClassSkippingTestRunner extends Runner implements Filterable {
-        private final TestClass mTestClass;
         private final Description mDescription;
         private boolean mFilteredOut;
 
-        ClassSkippingTestRunner(TestClass testClass) {
-            mTestClass = testClass;
-            mDescription = Description.createTestDescription(
-                    testClass.getJavaClass(), testClass.getJavaClass().getSimpleName());
+        ClassSkippingTestRunner(Class<?> testClass) {
+            mDescription = Description.createTestDescription(testClass, testClass.getSimpleName());
             mFilteredOut = false;
         }
 
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
index 6d8fb98..09ed12d 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
@@ -17,12 +17,14 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.NoRavenizer;
 import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -353,4 +355,34 @@
         public void test2() {
         }
     }
+
+    /**
+     * The test class is unloadable, but has a @DisabledOnRavenwood.
+     */
+    @RunWith(AndroidJUnit4.class)
+    @DisabledOnRavenwood
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest)
+    testIgnored: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest)
+    testSuiteFinished: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest)
+    testSuiteFinished: classes
+    testRunFinished: 0,0,0,1
+    """)
+    // CHECKSTYLE:ON
+    public static class ClassUnloadbleTest {
+        static {
+            Assert.fail("Class unloadable!");
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 4fe0592..4b732ac 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,7 +2,9 @@
 
 set noparent
 
-marvinramin@google.com
 vladokom@google.com
+marvinramin@google.com
+caen@google.com
+biswarupp@google.com
 ogunwale@google.com
 michaelwr@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 68d0ad2..a459ea9 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -173,6 +173,15 @@
                     "include-filter": "com.android.server.wm.BackgroundActivityStart*"
                 }
             ]
+        },
+        {
+            "name": "CtsOsTestCases",
+            "file_patterns": ["StorageManagerService\\.java"],
+            "options": [
+                {
+                    "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
+                }
+            ]
         }
    ]
 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f0cc09f..22ec790 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -73,6 +73,7 @@
 import static android.media.audio.Flags.roForegroundAudioControl;
 import static android.os.Process.THREAD_GROUP_BACKGROUND;
 import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW;
 import static android.os.Process.THREAD_GROUP_RESTRICTED;
 import static android.os.Process.THREAD_GROUP_TOP_APP;
 import static android.os.Process.THREAD_PRIORITY_DISPLAY;
@@ -116,6 +117,7 @@
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
 import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
 import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
 import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -1731,6 +1733,11 @@
                 // The recently used non-top visible freeform app.
                 schedGroup = SCHED_GROUP_TOP_APP;
                 mAdjType = "perceptible-freeform-activity";
+            } else if ((flags
+                    & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) {
+                // Currently the only case is from freeform apps which are not close to top.
+                schedGroup = SCHED_GROUP_FOREGROUND_WINDOW;
+                mAdjType = "vis-multi-window-activity";
             }
             foregroundActivities = true;
             mHasVisibleActivities = true;
@@ -3438,6 +3445,9 @@
                 case SCHED_GROUP_RESTRICTED:
                     processGroup = THREAD_GROUP_RESTRICTED;
                     break;
+                case SCHED_GROUP_FOREGROUND_WINDOW:
+                    processGroup = THREAD_GROUP_FOREGROUND_WINDOW;
+                    break;
                 default:
                     processGroup = THREAD_GROUP_DEFAULT;
                     break;
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 21842db..fb1c2e9 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -331,7 +331,7 @@
         void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
             ProcessRecordNode node = mLastNode[slot].mNext;
             final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
-            while (node != tail) {
+            while (node != null && node != tail) {
                 mTmpOomAdjusterArgs.mApp = node.mApp;
                 if (node.mApp == null) {
                     // TODO(b/336178916) - Temporary logging for root causing b/336178916.
@@ -365,7 +365,9 @@
                 }
                 // Save the next before calling callback, since that may change the node.mNext.
                 final ProcessRecordNode next = node.mNext;
-                callback.accept(mTmpOomAdjusterArgs);
+                if (mTmpOomAdjusterArgs.mApp != null) {
+                    callback.accept(mTmpOomAdjusterArgs);
+                }
                 // There are couple of cases:
                 // a) The current node is moved to another slot
                 //    - for this case, we'd need to keep using the "next" node.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cb918a0..00250b4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -303,6 +303,9 @@
     // Activity manager's version of Process.THREAD_GROUP_TOP_APP
     // Disambiguate between actual top app and processes bound to the top app
     static final int SCHED_GROUP_TOP_APP_BOUND = 4;
+    // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW
+    // The priority is like between default and top-app.
+    static final int SCHED_GROUP_FOREGROUND_WINDOW = 5;
 
     // The minimum number of cached apps we want to be able to keep around,
     // without empty apps being able to push them out of memory.
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 88ff7e4..2cadbc58 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -216,7 +216,7 @@
 
     @Override
     public void onBootPhase(int phase) {
-        if (phase != PHASE_BOOT_COMPLETED) {
+        if (phase != PHASE_BOOT_COMPLETED || mHandler == null) {
             return;
         }
         Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete");
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 38eb416..ddea285 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -109,7 +109,7 @@
     /**
      * Sets the HDR conversion mode for the device.
      *
-     * Returns the system preferred Hdr output type nn case when HDR conversion mode is
+     * Returns the system preferred HDR output type in case when HDR conversion mode is
      * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}.
      * Returns Hdr::INVALID in other cases.
      * @hide
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 93bd926..acf4db3 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -318,13 +318,16 @@
      */
     public Display.HdrCapabilities hdrCapabilities;
 
+    /** When true, all HDR capabilities are hidden from public APIs */
+    public boolean isForceSdr;
+
     /**
      * Indicates whether this display supports Auto Low Latency Mode.
      */
     public boolean allmSupported;
 
     /**
-     * Indicates whether this display suppors Game content type.
+     * Indicates whether this display supports Game content type.
      */
     public boolean gameContentTypeSupported;
 
@@ -516,6 +519,7 @@
                 || !Arrays.equals(supportedModes, other.supportedModes)
                 || !Arrays.equals(supportedColorModes, other.supportedColorModes)
                 || !Objects.equals(hdrCapabilities, other.hdrCapabilities)
+                || isForceSdr != other.isForceSdr
                 || allmSupported != other.allmSupported
                 || gameContentTypeSupported != other.gameContentTypeSupported
                 || densityDpi != other.densityDpi
@@ -560,6 +564,7 @@
         colorMode = other.colorMode;
         supportedColorModes = other.supportedColorModes;
         hdrCapabilities = other.hdrCapabilities;
+        isForceSdr = other.isForceSdr;
         allmSupported = other.allmSupported;
         gameContentTypeSupported = other.gameContentTypeSupported;
         densityDpi = other.densityDpi;
@@ -603,6 +608,7 @@
         sb.append(", colorMode ").append(colorMode);
         sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
         sb.append(", hdrCapabilities ").append(hdrCapabilities);
+        sb.append(", isForceSdr ").append(isForceSdr);
         sb.append(", allmSupported ").append(allmSupported);
         sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported);
         sb.append(", density ").append(densityDpi);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3c2167e..e7fd8f7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -48,6 +48,7 @@
 import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
 import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
 import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
 
 import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
 
@@ -284,7 +285,7 @@
     @GuardedBy("mSyncRoot")
     private int[] mUserDisabledHdrTypes = {};
     @Display.HdrCapabilities.HdrType
-    private int[] mSupportedHdrOutputType;
+    private int[] mSupportedHdrOutputTypes;
     @GuardedBy("mSyncRoot")
     private boolean mAreUserDisabledHdrTypesAllowed = true;
 
@@ -299,10 +300,10 @@
     // HDR conversion mode chosen by user
     @GuardedBy("mSyncRoot")
     private HdrConversionMode mHdrConversionMode = null;
-    // Actual HDR conversion mode, which takes app overrides into account.
-    private HdrConversionMode mOverrideHdrConversionMode = null;
+    // Whether app has disabled HDR conversion
+    private boolean mShouldDisableHdrConversion = false;
     @GuardedBy("mSyncRoot")
-    private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID;
+    private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID;
 
 
     // The synchronization root for the display manager.
@@ -1419,7 +1420,8 @@
         }
     }
 
-    private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
+    @VisibleForTesting
+    void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
         synchronized (mSyncRoot) {
             if (userDisabledHdrTypes == null) {
                 Slog.e(TAG, "Null is not an expected argument to "
@@ -1437,6 +1439,7 @@
             if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
                 return;
             }
+
             String userDisabledFormatsString = "";
             if (userDisabledHdrTypes.length != 0) {
                 userDisabledFormatsString = TextUtils.join(",",
@@ -1452,6 +1455,15 @@
                             handleLogicalDisplayChangedLocked(display);
                         });
             }
+            /* Note: it may be expected to reset the Conversion Mode when an HDR type is enabled
+             and the Conversion Mode is set to System Preferred. This is handled in the Settings
+             code because in the special case where HDR is indirectly disabled by Force SDR
+             Conversion, manually enabling HDR is not recognized as an action that reduces the
+             disabled HDR count. Thus, this case needs to be checked in the Settings code when we
+             know we're enabling an HDR mode. If we split checking for SystemConversion and
+             isForceSdr in two places, we may have duplicate calls to resetting to System Conversion
+             and get two black screens.
+             */
         }
     }
 
@@ -1464,19 +1476,20 @@
         return true;
     }
 
-    private void setAreUserDisabledHdrTypesAllowedInternal(
+    @VisibleForTesting
+    void setAreUserDisabledHdrTypesAllowedInternal(
             boolean areUserDisabledHdrTypesAllowed) {
         synchronized (mSyncRoot) {
             if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) {
                 return;
             }
             mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed;
-            if (mUserDisabledHdrTypes.length == 0) {
-                return;
-            }
             Settings.Global.putInt(mContext.getContentResolver(),
                     Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
                     areUserDisabledHdrTypesAllowed ? 1 : 0);
+            if (mUserDisabledHdrTypes.length == 0) {
+                return;
+            }
             int userDisabledHdrTypes[] = {};
             if (!mAreUserDisabledHdrTypesAllowed) {
                 userDisabledHdrTypes = mUserDisabledHdrTypes;
@@ -1487,6 +1500,14 @@
                         display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes);
                         handleLogicalDisplayChangedLocked(display);
                     });
+            // When HDR conversion mode is set to SYSTEM, modification to
+            // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell
+            // the system which HDR types it is not allowed to use.
+            if (getHdrConversionModeInternal().getConversionMode()
+                    == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
+                setHdrConversionModeInternal(
+                        new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+            }
         }
     }
 
@@ -2357,7 +2378,7 @@
         final int preferredHdrOutputType =
                 hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE
                         ? hdrConversionMode.getPreferredHdrOutputType()
-                        : Display.HdrCapabilities.HDR_TYPE_INVALID;
+                        : HDR_TYPE_INVALID;
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType);
     }
@@ -2370,7 +2391,7 @@
                 ? Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.HDR_FORCE_CONVERSION_TYPE,
                         Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
-                : Display.HdrCapabilities.HDR_TYPE_INVALID;
+                : HDR_TYPE_INVALID;
         mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType);
         setHdrConversionModeInternal(mHdrConversionMode);
     }
@@ -2507,22 +2528,38 @@
         });
     }
 
+    /**
+     * Returns the HDR output types that are supported by the device's HDR conversion capabilities,
+     * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false.
+     */
     @GuardedBy("mSyncRoot")
-    private int[] getEnabledAutoHdrTypesLocked() {
-        IntArray autoHdrOutputTypesArray = new IntArray();
+    @VisibleForTesting
+    int[] getEnabledHdrOutputTypesLocked() {
+        if (mAreUserDisabledHdrTypesAllowed) {
+            return getSupportedHdrOutputTypesInternal();
+        }
+        // Strip out all HDR formats that are currently user-disabled
+        IntArray enabledHdrOutputTypesArray = new IntArray();
         for (int type : getSupportedHdrOutputTypesInternal()) {
-            boolean isDisabled = false;
+            boolean isEnabled = true;
             for (int disabledType : mUserDisabledHdrTypes) {
                 if (type == disabledType) {
-                    isDisabled = true;
+                    isEnabled = false;
                     break;
                 }
             }
-            if (!isDisabled) {
-                autoHdrOutputTypesArray.add(type);
+            if (isEnabled) {
+                enabledHdrOutputTypesArray.add(type);
             }
         }
-        return autoHdrOutputTypesArray.toArray();
+        return enabledHdrOutputTypesArray.toArray();
+    }
+
+    @VisibleForTesting
+    int[] getEnabledHdrOutputTypes() {
+        synchronized (mSyncRoot) {
+            return getEnabledHdrOutputTypesLocked();
+        }
     }
 
     @GuardedBy("mSyncRoot")
@@ -2531,7 +2568,7 @@
         final int preferredHdrOutputType =
                 mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
                         ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
-        if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+        if (preferredHdrOutputType != HDR_TYPE_INVALID) {
             int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
             return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
         }
@@ -2565,41 +2602,57 @@
         if (!mInjector.getHdrOutputConversionSupport()) {
             return;
         }
-        int[] autoHdrOutputTypes = null;
+
         synchronized (mSyncRoot) {
             if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
                     && hdrConversionMode.getPreferredHdrOutputType()
-                    != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+                    != HDR_TYPE_INVALID) {
                 throw new IllegalArgumentException("preferredHdrOutputType must not be set if"
                         + " the conversion mode is HDR_CONVERSION_SYSTEM");
             }
             mHdrConversionMode = hdrConversionMode;
             storeHdrConversionModeLocked(mHdrConversionMode);
 
-            // For auto mode, all supported HDR types are allowed except the ones specifically
-            // disabled by the user.
+            // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed
+            // except the ones specifically disabled by the user.
+            int[] enabledHdrOutputTypes = null;
             if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
-                autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
+                enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked();
             }
 
             int conversionMode = hdrConversionMode.getConversionMode();
             int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType();
+
             // If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then
             // set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH.
-            if (mOverrideHdrConversionMode == null) {
-                // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
-                // But, internally SDR is selected by using passthrough mode.
-                if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
-                        && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) {
-                    conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
-                }
+            if (mShouldDisableHdrConversion) {
+                conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+                preferredHdrType = -1;
+                enabledHdrOutputTypes = null;
             } else {
-                conversionMode = mOverrideHdrConversionMode.getConversionMode();
-                preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType();
-                autoHdrOutputTypes = null;
+                // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
+                // But, internally SDR is forced by using passthrough mode and not reporting any
+                // HDR capabilities to apps.
+                if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
+                        && preferredHdrType == HDR_TYPE_INVALID) {
+                    conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+                    mLogicalDisplayMapper.forEachLocked(
+                            logicalDisplay -> {
+                                if (logicalDisplay.setIsForceSdr(true)) {
+                                    handleLogicalDisplayChangedLocked(logicalDisplay);
+                                }
+                            });
+                } else {
+                    mLogicalDisplayMapper.forEachLocked(
+                            logicalDisplay -> {
+                                if (logicalDisplay.setIsForceSdr(false)) {
+                                    handleLogicalDisplayChangedLocked(logicalDisplay);
+                                }
+                            });
+                }
             }
             mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode(
-                    conversionMode, preferredHdrType, autoHdrOutputTypes);
+                    conversionMode, preferredHdrType, enabledHdrOutputTypes);
         }
     }
 
@@ -2621,8 +2674,8 @@
         }
         HdrConversionMode mode;
         synchronized (mSyncRoot) {
-            mode = mOverrideHdrConversionMode != null
-                    ? mOverrideHdrConversionMode
+            mode = mShouldDisableHdrConversion
+                    ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH)
                     : mHdrConversionMode;
             // Handle default: PASSTHROUGH. Don't include the system-preferred type.
             if (mode == null
@@ -2630,8 +2683,6 @@
                 return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
             }
             // Handle default or current mode: SYSTEM. Include the system preferred type.
-            // mOverrideHdrConversionMode and mHdrConversionMode do not include the system
-            // preferred type, it is kept separately in mSystemPreferredHdrOutputType.
             if (mode == null
                     || mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
                 return new HdrConversionMode(
@@ -2642,10 +2693,10 @@
     }
 
     private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
-        if (mSupportedHdrOutputType == null) {
-            mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
+        if (mSupportedHdrOutputTypes == null) {
+            mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes();
         }
-        return mSupportedHdrOutputType;
+        return mSupportedHdrOutputTypes;
     }
 
     void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
@@ -2831,15 +2882,9 @@
             // HDR conversion is disabled in two cases:
             // - HDR conversion introduces latency and minimal post-processing is requested
             // - app requests to disable HDR conversion
-            if (mOverrideHdrConversionMode == null && (disableHdrConversion
-                    || disableHdrConversionForLatency)) {
-                mOverrideHdrConversionMode =
-                            new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
-                setHdrConversionModeInternal(mHdrConversionMode);
-                handleLogicalDisplayChangedLocked(display);
-            } else if (mOverrideHdrConversionMode != null && !disableHdrConversion
-                    && !disableHdrConversionForLatency) {
-                mOverrideHdrConversionMode = null;
+            boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion;
+            mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency;
+            if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) {
                 setHdrConversionModeInternal(mHdrConversionMode);
                 handleLogicalDisplayChangedLocked(display);
             }
@@ -3530,9 +3575,9 @@
         }
 
         int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
-                int[] autoHdrTypes) {
+                int[] allowedHdrOutputTypes) {
             return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType,
-                    autoHdrTypes);
+                    allowedHdrOutputTypes);
         }
 
         @Display.HdrCapabilities.HdrType
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index e8be8a4..007e3a8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -518,6 +518,7 @@
                     deviceInfo.supportedColorModes,
                     deviceInfo.supportedColorModes.length);
             mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities;
+            mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr;
             mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes;
             mBaseDisplayInfo.minimalPostProcessingSupported =
                     deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported;
@@ -899,6 +900,29 @@
     }
 
     /**
+     * Checks whether display is of the type where HDR settings are relevant, and then sets
+     * whether Force SDR conversion mode is active.  isForceSdr is checked by the Display when
+     * returning HDR capabilities.
+     *
+     * @param isForceSdr Whether Force SDR conversion mode is active
+     * @return Whether Display Manager should call handleLogicalDisplayChangedLocked()
+     */
+    public boolean setIsForceSdr(boolean isForceSdr) {
+        int displayType = getDisplayInfoLocked().type;
+        boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL
+                || displayType == Display.TYPE_EXTERNAL
+                || displayType == Display.TYPE_OVERLAY;
+
+        boolean handleLogicalDisplayChangedLocked = false;
+        if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) {
+            mBaseDisplayInfo.isForceSdr = isForceSdr;
+            mInfo.set(null);
+            handleLogicalDisplayChangedLocked = true;
+        }
+        return handleLogicalDisplayChangedLocked;
+    }
+
+    /**
      * Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay.
      *
      * @param targetDisplay The display with which to swap display-devices.
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index ea240c7..9d04682 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,12 +196,7 @@
                 File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
                 if (!signatureFile.exists()) {
                     Slog.i(TAG, "The signature file is missing.");
-                    if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
-                        return;
-                    } else {
-                        FileUtils.deleteContentsAndDir(dir);
-                        continue;
-                    }
+                    return;
                 }
                 byte[] signature;
                 try {
@@ -226,39 +221,33 @@
 
                 FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
                 if (fontConfig == null) {
-                    if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
-                        // Use preinstalled font config for checking revision number.
-                        fontConfig = mConfigSupplier.apply(Collections.emptyMap());
-                    } else {
-                        fontConfig = getSystemFontConfig();
-                    }
+                    // Use preinstalled font config for checking revision number.
+                    fontConfig = mConfigSupplier.apply(Collections.emptyMap());
                 }
                 addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
             }
 
-            if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
-                // Treat as error if post script name of font family was not installed.
-                for (int i = 0; i < config.fontFamilies.size(); ++i) {
-                    FontUpdateRequest.Family family = config.fontFamilies.get(i);
-                    for (int j = 0; j < family.getFonts().size(); ++j) {
-                        FontUpdateRequest.Font font = family.getFonts().get(j);
-                        if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
-                            continue;
-                        }
-
-                        if (fontConfig == null) {
-                            fontConfig = mConfigSupplier.apply(Collections.emptyMap());
-                        }
-
-                        if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
-                            continue;
-                        }
-
-                        Slog.e(TAG, "Unknown font that has PostScript name "
-                                + font.getPostScriptName() + " is requested in FontFamily "
-                                + family.getName());
-                        return;
+            // Treat as error if post script name of font family was not installed.
+            for (int i = 0; i < config.fontFamilies.size(); ++i) {
+                FontUpdateRequest.Family family = config.fontFamilies.get(i);
+                for (int j = 0; j < family.getFonts().size(); ++j) {
+                    FontUpdateRequest.Font font = family.getFonts().get(j);
+                    if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+                        continue;
                     }
+
+                    if (fontConfig == null) {
+                        fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+                    }
+
+                    if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+                        continue;
+                    }
+
+                    Slog.e(TAG, "Unknown font that has PostScript name "
+                            + font.getPostScriptName() + " is requested in FontFamily "
+                            + family.getName());
+                    return;
                 }
             }
 
@@ -273,9 +262,7 @@
                 mFontFileInfoMap.clear();
                 mLastModifiedMillis = 0;
                 FileUtils.deleteContents(mFilesDir);
-                if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
-                    mConfigFile.delete();
-                }
+                mConfigFile.delete();
             }
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index ac75ef7..8e41d18 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1688,7 +1688,11 @@
     private void sendCecCommandWithRetries(HdmiCecMessage command,
             @Nullable SendMessageCallback callback) {
         assertRunOnServiceThread();
-        HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
+        List<HdmiCecLocalDevice> devices = getAllCecLocalDevices();
+        if (devices.isEmpty()) {
+            return;
+        }
+        HdmiCecLocalDevice localDevice = devices.get(0);
         if (localDevice != null) {
             sendCecCommandWithoutRetries(command, new SendMessageCallback() {
                 @Override
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index edc35e5..65adaba 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -456,6 +456,14 @@
         void registerLocalService(InputManagerInternal localService) {
             LocalServices.addService(InputManagerInternal.class, localService);
         }
+
+        KeyboardBacklightControllerInterface getKeyboardBacklightController(
+                NativeInputManagerService nativeService, PersistentDataStore dataStore) {
+            return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
+                    ? new KeyboardBacklightController(mContext, nativeService, dataStore,
+                    mLooper, mUEventManager)
+                    : new KeyboardBacklightControllerInterface() {};
+        }
     }
 
     public InputManagerService(Context context) {
@@ -479,10 +487,7 @@
                         injector.getLooper(), this) : null;
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
                 injector.getUEventManager());
-        mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
-                ? new KeyboardBacklightController(mContext, mNative, mDataStore,
-                        injector.getLooper(), injector.getUEventManager())
-                : new KeyboardBacklightControllerInterface() {};
+        mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore);
         mStickyModifierStateController = new StickyModifierStateController();
         mKeyGestureController = new KeyGestureController(mContext, injector.getLooper());
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
@@ -606,6 +611,8 @@
         mKeyRemapper.systemRunning();
         mPointerIconCache.systemRunning();
         mKeyboardGlyphManager.systemRunning();
+
+        initKeyGestures();
     }
 
     private void reloadDeviceAliases() {
@@ -2504,6 +2511,59 @@
                 null, null, null) == PERMISSION_GRANTED;
     }
 
+    private void initKeyGestures() {
+        if (!useKeyGestureEventHandler()) {
+            return;
+        }
+        InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+        im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
+            @Override
+            public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
+                    @Nullable IBinder focussedToken) {
+                int deviceId = event.getDeviceId();
+                boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                        && !event.isCancelled();
+                switch (event.getKeyGestureType()) {
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+                        if (complete) {
+                            mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
+                        }
+                        return true;
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+                        if (complete) {
+                            mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
+                        }
+                        return true;
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+                        // TODO(b/367748270): Add functionality to turn keyboard backlight on/off.
+                        return true;
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+                        if (complete) {
+                            mNative.toggleCapsLock(deviceId);
+                        }
+                        return true;
+                    default:
+                        return false;
+
+                }
+            }
+
+            @Override
+            public boolean isKeyGestureSupported(int gestureType) {
+                switch (gestureType) {
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+                        return true;
+                    default:
+                        return false;
+
+                }
+            }
+        });
+    }
+
     // Native callback.
     @SuppressWarnings("unused")
     private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
@@ -3207,6 +3267,8 @@
             mKeyboardBacklightController.onInteractiveChanged(interactive);
         }
 
+        // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+        //  handler refactoring complete
         @Override
         public void toggleCapsLock(int deviceId) {
             mNative.toggleCapsLock(deviceId);
@@ -3294,11 +3356,15 @@
             mKeyboardBacklightController.notifyUserActivity();
         }
 
+        // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+        //  handler refactoring complete
         @Override
         public void incrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
         }
 
+        // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+        //  handler refactoring complete
         @Override
         public void decrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 5ff8568..0e940d2 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -180,6 +180,10 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) {
+            return false;
+        }
+
         float deltaX;
         float deltaY;
         switch (event.getAction()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 4b2c12a..63bd9ab 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -389,7 +389,7 @@
             // Reload gnss config for no SIM case
             mGnssConfiguration.reloadGpsProperties();
         }
-        if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+        if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
             updateNiSuplMessageListenerRegistration(
                     mGnssConfiguration.isNiSuplMessageInjectionEnabled());
         }
@@ -538,7 +538,7 @@
         intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
 
-        if (!Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+        if (!Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
             updateNiSuplMessageListenerRegistration(
                     mGnssConfiguration.isNiSuplMessageInjectionEnabled());
         }
@@ -1672,7 +1672,7 @@
         if (dumpAll) {
             mNetworkTimeHelper.dump(pw);
             pw.println("mSupportsPsds=" + mSupportsPsds);
-            if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+            if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
                 pw.println("mNiSuplMessageListenerRegistered="
                         + mNiSuplMessageListenerRegistered);
             }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 158d444..1e25f1c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -140,7 +140,7 @@
         try {
             db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
                     new String[] {key, Integer.toString(userId)});
-            db.insert(TABLE, null, cv);
+            db.insertOrThrow(TABLE, null, cv);
             db.setTransactionSuccessful();
             mCache.putKeyValue(key, value, userId);
         } finally {
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 621c090..48d24f2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -179,7 +179,8 @@
     /**
      * 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
+     *   - be one of the bugreport allowlisted packages, or
+     *   - hold the OP_PROJECT_MEDIA AppOp.
      */
     private boolean canCaptureKeyguard() {
         if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
@@ -194,6 +195,14 @@
                     == PackageManager.PERMISSION_GRANTED) {
                 return true;
             }
+            boolean operationActive = mAppOps.isOperationActive(AppOpsManager.OP_PROJECT_MEDIA,
+                    mProjectionGrant.uid,
+                    mProjectionGrant.packageName);
+            if (operationActive) {
+                // Some tools use media projection by granting the OP_PROJECT_MEDIA app
+                // op via a shell command. Those tools can be granted keyguard capture
+                return true;
+            }
             return SystemConfig.getInstance().getBugreportWhitelistedPackages()
                     .contains(mProjectionGrant.packageName);
         }
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index fbe7772..f54c1f7 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -218,10 +218,16 @@
      * @param uri {@code Uri} an uri including query parameter "vibraiton_uri"
      */
     public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) {
-        if (uri == null) {
+        if (uri == null || uri.isOpaque()) {
             return null;
         }
-        return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+
+        try {
+            return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to get vibration effect: ", e);
+        }
+        return null;
     }
 
     /** Returns if a given vibration can be played by the vibrator that does notification buzz. */
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a683a8c..89417f3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1090,6 +1090,21 @@
         mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
         mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
         emulateSystemUserModeIfNeeded();
+        initPropertyInvalidatedCaches();
+    }
+
+    /**
+     * This method is used to invalidate the caches at server statup,
+     * so that caches can start working.
+     */
+    private static final void initPropertyInvalidatedCaches() {
+        if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
+            UserManager.invalidateIsUserUnlockedCache();
+            UserManager.invalidateQuietModeEnabledCache();
+            UserManager.invalidateStaticUserProperties();
+            UserManager.invalidateUserPropertiesCache();
+            UserManager.invalidateUserSerialNumberCache();
+        }
     }
 
     private boolean doesDeviceHardwareSupportPrivateSpace() {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ef37464..02c02b0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3849,10 +3849,6 @@
                     case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
                         return true;
                     default:
                         return false;
@@ -4010,25 +4006,6 @@
                     sendSwitchKeyboardLayout(displayId, focusedToken, direction);
                 }
                 return true;
-            // TODO (b/358569822): Move these input specific gesture handling to IMS since we
-            //  are calling into InputManager through internal API anyways
-            case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
-                if (complete) {
-                    mInputManagerInternal.incrementKeyboardBacklight(deviceId);
-                }
-                return true;
-            case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
-                if (complete) {
-                    mInputManagerInternal.decrementKeyboardBacklight(deviceId);
-                }
-                return true;
-            case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
-                return true;
-            case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
-                if (complete) {
-                    mInputManagerInternal.toggleCapsLock(deviceId);
-                }
-                return true;
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 68026ea..e3d71e4 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -24,8 +24,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -78,6 +81,8 @@
 public final class RollbackPackageHealthObserver implements PackageHealthObserver {
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
+    private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
 
@@ -596,12 +601,40 @@
             }
         };
 
-        final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> {
-            mHandler.post(() -> onResult.accept(result));
-        });
+        if (Flags.refactorCrashrecovery()) {
+            // Define a BroadcastReceiver to handle the result
+            BroadcastReceiver rollbackReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent result) {
+                    mHandler.post(() -> onResult.accept(result));
+                }
+            };
 
-        rollbackManager.commitRollback(rollback.getRollbackId(),
-                Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
+            // Register the BroadcastReceiver
+            mContext.registerReceiver(rollbackReceiver,
+                    new IntentFilter(ACTION_NAME),
+                    Context.RECEIVER_NOT_EXPORTED);
+
+            Intent intentReceiver = new Intent(ACTION_NAME);
+            intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
+            intentReceiver.setPackage(mContext.getPackageName());
+
+            PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
+                    rollback.getRollbackId(),
+                    intentReceiver,
+                    PendingIntent.FLAG_MUTABLE);
+
+            rollbackManager.commitRollback(rollback.getRollbackId(),
+                    Collections.singletonList(failedPackage),
+                    rollbackPendingIntent.getIntentSender());
+        } else {
+            final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> {
+                mHandler.post(() -> onResult.accept(result));
+            });
+
+            rollbackManager.commitRollback(rollback.getRollbackId(),
+                    Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 96a25da..1e82b89 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -322,9 +322,16 @@
 
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
             // Get only configs as needed to save memory.
-            final PersistableBundle carrierConfig =
-                    CarrierConfigManager.getCarrierConfigSubset(mContext, subId,
-                            VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+            PersistableBundle carrierConfig = new PersistableBundle();
+            try {
+                carrierConfig =
+                        mCarrierConfigManager.getConfigForSubId(
+                                subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+
+            } catch (RuntimeException exception) {
+                Slog.w(TAG, "CarrierConfigLoader is not available.");
+            }
+
             if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
                 mReadySubIdsBySlotId.put(slotId, subId);
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index a698429..15f86e9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -78,7 +78,7 @@
     /**
      * The component name of the currently set live wallpaper.
      */
-    ComponentName wallpaperComponent;
+    private ComponentName mWallpaperComponent;
 
     // TODO(b/347235611) Remove this field
     /**
@@ -195,7 +195,7 @@
      */
     WallpaperData(WallpaperData source) {
         this.userId = source.userId;
-        this.wallpaperComponent = source.wallpaperComponent;
+        this.mWallpaperComponent = source.mWallpaperComponent;
         this.mWhich = source.mWhich;
         this.wallpaperId = source.wallpaperId;
         this.cropHint.set(source.cropHint);
@@ -230,6 +230,14 @@
         return result;
     }
 
+    ComponentName getComponent() {
+        return mWallpaperComponent;
+    }
+
+    void setComponent(ComponentName componentName) {
+        this.mWallpaperComponent = componentName;
+    }
+
     @Override
     public String toString() {
         StringBuilder out = new StringBuilder(defaultString(this));
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index b15facb..e3e83b3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -189,13 +189,13 @@
 
                         String comp = parser.getAttributeValue(null, "component");
                         if (removeNextWallpaperComponent()) {
-                            wallpaperToParse.wallpaperComponent = comp != null
+                            wallpaperToParse.setComponent(comp != null
                                     ? ComponentName.unflattenFromString(comp)
-                                    : null;
-                            if (wallpaperToParse.wallpaperComponent == null
-                                    || "android".equals(wallpaperToParse.wallpaperComponent
+                                    : null);
+                            if (wallpaperToParse.getComponent() == null
+                                    || "android".equals(wallpaperToParse.getComponent()
                                     .getPackageName())) {
-                                wallpaperToParse.wallpaperComponent = mImageWallpaper;
+                                wallpaperToParse.setComponent(mImageWallpaper);
                             }
                         } else {
                             wallpaperToParse.nextWallpaperComponent = comp != null
@@ -219,7 +219,7 @@
                             Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors);
                             Slog.v(TAG, "mName:" + wallpaper.name);
                             if (removeNextWallpaperComponent()) {
-                                Slog.v(TAG, "mWallpaperComponent:" + wallpaper.wallpaperComponent);
+                                Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent());
                             } else {
                                 Slog.v(TAG, "mNextWallpaperComponent:"
                                         + wallpaper.nextWallpaperComponent);
@@ -340,7 +340,7 @@
                 getAttributeInt(parser, "totalCropTop", 0),
                 getAttributeInt(parser, "totalCropRight", 0),
                 getAttributeInt(parser, "totalCropBottom", 0));
-        ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.wallpaperComponent
+        ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent()
                 : wallpaper.nextWallpaperComponent;
         if (multiCrop() && mImageWallpaper.equals(componentName)) {
             wallpaper.mCropHints = new SparseArray<>();
@@ -480,7 +480,7 @@
         out.startTag(null, tag);
         out.attributeInt(null, "id", wallpaper.wallpaperId);
 
-        if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+        if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) {
             if (wallpaper.mCropHints == null) {
                 Slog.e(TAG, "cropHints should not be null when saved");
                 wallpaper.mCropHints = new SparseArray<>();
@@ -580,10 +580,10 @@
         }
 
         out.attribute(null, "name", wallpaper.name);
-        if (wallpaper.wallpaperComponent != null
-                && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
+        if (wallpaper.getComponent() != null
+                && !wallpaper.getComponent().equals(mImageWallpaper)) {
             out.attribute(null, "component",
-                    wallpaper.wallpaperComponent.flattenToShortString());
+                    wallpaper.getComponent().flattenToShortString());
         }
 
         if (wallpaper.allowBackup) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6cc37dd..4754ffb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -276,7 +276,7 @@
             final boolean isMigration = moved && lockWallpaperChanged;
             final boolean isRestore = moved && !isMigration;
             final boolean isAppliedToLock = (wallpaper.mWhich & FLAG_LOCK) != 0;
-            final boolean needsUpdate = wallpaper.wallpaperComponent == null
+            final boolean needsUpdate = wallpaper.getComponent() == null
                     || event != CLOSE_WRITE // includes the MOVED_TO case
                     || wallpaper.imageWallpaperPending;
 
@@ -527,7 +527,7 @@
      * @return true unless the wallpaper changed during the color computation
      */
     private boolean extractColors(WallpaperData wallpaper) {
-        if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent);
+        if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent());
         String cropFile = null;
         boolean defaultImageWallpaper = false;
         int wallpaperId;
@@ -550,8 +550,8 @@
 
         synchronized (mLock) {
             // Not having a wallpaperComponent means it's a lock screen wallpaper.
-            final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
-                    || wallpaper.wallpaperComponent == null;
+            final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.getComponent())
+                    || wallpaper.getComponent() == null;
             if (imageWallpaper && wallpaper.getCropFile().exists()) {
                 cropFile = wallpaper.getCropFile().getAbsolutePath();
             } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
@@ -824,13 +824,13 @@
                 return;
             }
             TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
-            t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
+            t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent());
             if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
             mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
                     null /* options */);
             mWindowManagerInternal.setWallpaperShowWhenLocked(
                     mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
-            if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+            if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) {
                 mWindowManagerInternal.setWallpaperCropHints(mToken,
                         mWallpaperCropper.getRelativeCropHints(wallpaper));
             } else {
@@ -906,7 +906,7 @@
                 }
 
                 if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
-                    Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+                    Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.getComponent()
                             + ", reverting to built-in wallpaper!");
                     clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
                 }
@@ -1035,9 +1035,9 @@
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mLock) {
                 Slog.w(TAG, "Wallpaper service gone: " + name);
-                if (!Objects.equals(name, mWallpaper.wallpaperComponent)) {
+                if (!Objects.equals(name, mWallpaper.getComponent())) {
                     Slog.e(TAG, "Does not match expected wallpaper component "
-                            + mWallpaper.wallpaperComponent);
+                            + mWallpaper.getComponent());
                 }
                 mService = null;
                 forEachDisplayConnector(connector -> connector.mEngine = null);
@@ -1065,7 +1065,7 @@
             fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS);
             if (DEBUG_LIVE) {
                 Slog.i(TAG,
-                        "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent);
+                        "Started wallpaper reconnect timeout for " + mWallpaper.getComponent());
             }
         }
 
@@ -1081,7 +1081,7 @@
                     return;
                 }
 
-                final ComponentName wpService = mWallpaper.wallpaperComponent;
+                final ComponentName wpService = mWallpaper.getComponent();
                 // The broadcast of package update could be delayed after service disconnected. Try
                 // to re-bind the service for 10 seconds.
                 mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND;
@@ -1110,7 +1110,7 @@
                 // The wallpaper disappeared.  If this isn't a system-default one, track
                 // crashes and fall back to default if it continues to misbehave.
                 if (this == mWallpaper.connection) {
-                    final ComponentName wpService = mWallpaper.wallpaperComponent;
+                    final ComponentName wpService = mWallpaper.getComponent();
                     if (!mWallpaper.wallpaperUpdating
                             && mWallpaper.userId == mCurrentUserId
                             && !Objects.equals(mDefaultWallpaperComponent, wpService)
@@ -1188,7 +1188,7 @@
             synchronized (mLock) {
                 // Do not broadcast changes on ImageWallpaper since it's handled
                 // internally by this class.
-                boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent);
+                boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.getComponent());
                 if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) {
                     return;
                 }
@@ -1303,7 +1303,7 @@
                 if (mNewWallpaper.mWhich == FLAG_SYSTEM) {
                     // New wp is system only, so old system+lock is now lock only
                     final boolean originalIsStatic = mImageWallpaper.equals(
-                            mOriginalSystem.wallpaperComponent);
+                            mOriginalSystem.getComponent());
                     if (originalIsStatic) {
                         // Static wp: image file rename has already been tried via
                         // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map
@@ -1314,8 +1314,7 @@
                             if (DEBUG) {
                                 Slog.v(TAG, "static system+lock to system success");
                             }
-                            lockWp.wallpaperComponent =
-                                    mOriginalSystem.wallpaperComponent;
+                            lockWp.setComponent(mOriginalSystem.getComponent());
                             lockWp.connection = mOriginalSystem.connection;
                             lockWp.connection.mWallpaper = lockWp;
                             mOriginalSystem.mWhich = FLAG_LOCK;
@@ -1376,7 +1375,7 @@
                     return;
                 }
                 for (WallpaperData wallpaper: getWallpapers()) {
-                    final ComponentName wpService = wallpaper.wallpaperComponent;
+                    final ComponentName wpService = wallpaper.getComponent();
                     if (wpService != null && wpService.getPackageName().equals(packageName)) {
                         if (DEBUG_LIVE) {
                             Slog.i(TAG, "Wallpaper " + wpService + " update has finished");
@@ -1402,8 +1401,8 @@
                     return;
                 }
                 for (WallpaperData wallpaper: getWallpapers()) {
-                    if (wallpaper.wallpaperComponent != null
-                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+                    if (wallpaper.getComponent() != null
+                            && wallpaper.getComponent().getPackageName().equals(packageName)) {
                         doPackagesChangedLocked(true, wallpaper);
                     }
                 }
@@ -1417,10 +1416,10 @@
                     return;
                 }
                 for (WallpaperData wallpaper: getWallpapers()) {
-                    if (wallpaper.wallpaperComponent != null
-                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+                    if (wallpaper.getComponent() != null
+                            && wallpaper.getComponent().getPackageName().equals(packageName)) {
                         if (DEBUG_LIVE) {
-                            Slog.i(TAG, "Wallpaper service " + wallpaper.wallpaperComponent
+                            Slog.i(TAG, "Wallpaper service " + wallpaper.getComponent()
                                     + " is updating");
                         }
                         wallpaper.wallpaperUpdating = true;
@@ -1462,15 +1461,15 @@
 
         boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
             boolean changed = false;
-            if (wallpaper.wallpaperComponent != null) {
-                int change = isPackageDisappearing(wallpaper.wallpaperComponent
+            if (wallpaper.getComponent() != null) {
+                int change = isPackageDisappearing(wallpaper.getComponent()
                         .getPackageName());
                 if (change == PACKAGE_PERMANENT_CHANGE
                         || change == PACKAGE_TEMPORARY_CHANGE) {
                     changed = true;
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
-                                + wallpaper.wallpaperComponent);
+                                + wallpaper.getComponent());
                         clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                     }
                 }
@@ -1485,15 +1484,15 @@
                     }
                 }
             }
-            if (wallpaper.wallpaperComponent != null
-                    && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) {
+            if (wallpaper.getComponent() != null
+                    && isPackageModified(wallpaper.getComponent().getPackageName())) {
                 try {
-                    mContext.getPackageManager().getServiceInfo(wallpaper.wallpaperComponent,
+                    mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(),
                             PackageManager.MATCH_DIRECT_BOOT_AWARE
                                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
                 } catch (NameNotFoundException e) {
                     Slog.w(TAG, "Wallpaper component gone, removing: "
-                            + wallpaper.wallpaperComponent);
+                            + wallpaper.getComponent());
                     clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                 }
             }
@@ -1636,8 +1635,8 @@
         // sure we have something to render
         boolean isImageComponent;
         if (removeNextWallpaperComponent()) {
-            isImageComponent = wallpaper.wallpaperComponent == null
-                    || mImageWallpaper.equals(wallpaper.wallpaperComponent);
+            isImageComponent = wallpaper.getComponent() == null
+                    || mImageWallpaper.equals(wallpaper.getComponent());
         } else {
             isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent);
         }
@@ -1892,10 +1891,10 @@
 
             final ComponentName cname;
             if (removeNextWallpaperComponent()) {
-                cname = wallpaper.wallpaperComponent;
+                cname = wallpaper.getComponent();
             } else {
-                cname = (wallpaper.wallpaperComponent != null)
-                        ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
+                cname = (wallpaper.getComponent() != null)
+                        ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent;
             }
             if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
                 // We failed to bind the desired wallpaper, but that might
@@ -1927,7 +1926,7 @@
             // We might end up persisting the current wallpaper data
             // while locked, so pretend like the component was actually
             // bound into place
-            wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
+            wallpaper.setComponent(wallpaper.nextWallpaperComponent);
         }
         final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich);
 
@@ -2004,7 +2003,7 @@
 
             // lock only case: set the system wallpaper component to both screens
             if (which == FLAG_LOCK) {
-                component = wallpaper.wallpaperComponent;
+                component = wallpaper.getComponent();
                 finalWhich = FLAG_LOCK | FLAG_SYSTEM;
             } else {
                 component = null;
@@ -2310,7 +2309,7 @@
             checkPermission(READ_WALLPAPER_INTERNAL);
             WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
                     : mWallpaperMap.get(userId);
-            if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+            if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) {
                 return null;
             }
             SparseArray<Rect> relativeSuggestedCrops =
@@ -2760,7 +2759,7 @@
             WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap)
                     .get(mCurrentUserId);
             if (wallpaperData == null) return false;
-            return mImageWallpaper.equals(wallpaperData.wallpaperComponent);
+            return mImageWallpaper.equals(wallpaperData.getComponent());
         }
     }
 
@@ -2996,7 +2995,7 @@
             final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId);
             final boolean systemIsStatic =
                     originalSystemWallpaper != null && mImageWallpaper.equals(
-                            originalSystemWallpaper.wallpaperComponent);
+                            originalSystemWallpaper.getComponent());
             final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null;
 
             /* If we're setting system but not lock, and lock is currently sharing the system
@@ -3190,7 +3189,7 @@
                 throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
             }
             final boolean systemIsStatic = mImageWallpaper.equals(
-                    originalSystemWallpaper.wallpaperComponent);
+                    originalSystemWallpaper.getComponent());
             final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null;
 
             if (which == FLAG_SYSTEM && systemIsBoth && systemIsStatic) {
@@ -3212,7 +3211,7 @@
                         liveSync = new WallpaperDestinationChangeHandler(
                         newWallpaper);
                 boolean same = changingToSame(name, newWallpaper.connection,
-                        newWallpaper.wallpaperComponent);
+                        newWallpaper.getComponent());
 
                 /*
                  * If we have a shared system+lock wallpaper, and we reapply the same wallpaper
@@ -3243,7 +3242,7 @@
                         }
                     }
                     boolean lockBitmapCleared = false;
-                    if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) {
+                    if (!mImageWallpaper.equals(newWallpaper.getComponent())) {
                         clearWallpaperBitmaps(newWallpaper);
                         lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK;
                     }
@@ -3324,7 +3323,7 @@
         }
         // Has the component changed?
         if (!force && changingToSame(componentName, wallpaper.connection,
-                wallpaper.wallpaperComponent)) {
+                wallpaper.getComponent())) {
             try {
                 if (DEBUG_LIVE) {
                     Slog.v(TAG, "Changing to the same component, ignoring");
@@ -3461,7 +3460,7 @@
                 return false;
             }
             maybeDetachLastWallpapers(wallpaper);
-            wallpaper.wallpaperComponent = componentName;
+            wallpaper.setComponent(componentName);
             wallpaper.connection = newConn;
             newConn.mReply = reply;
             updateCurrentWallpapers(wallpaper);
@@ -3586,7 +3585,7 @@
     }
 
     private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
-        wallpaper.wallpaperComponent = null;
+        wallpaper.setComponent(null);
         detachWallpaperLocked(wallpaper);
     }
 
@@ -3831,7 +3830,7 @@
             wallpaper.wallpaperId = makeWallpaperIdLocked();    // always bump id at restore
             wallpaper.allowBackup = true;   // by definition if it was restored
             ComponentName componentName =
-                    removeNextWallpaperComponent() ? wallpaper.wallpaperComponent
+                    removeNextWallpaperComponent() ? wallpaper.getComponent()
                             : wallpaper.nextWallpaperComponent;
             if (componentName != null && !componentName.equals(mImageWallpaper)) {
                 wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS;
@@ -3907,7 +3906,7 @@
         if (multiCrop()) pw.print("  mCropHints="); pw.println(wallpaper.mCropHints);
         pw.print("  mName=");  pw.println(wallpaper.name);
         pw.print("  mAllowBackup="); pw.println(wallpaper.allowBackup);
-        pw.print("  mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
+        pw.print("  mWallpaperComponent="); pw.println(wallpaper.getComponent());
         pw.print("  mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
         pw.print("  isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
         pw.println("  mUidToDimAmount:");
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 1994174..3a2cffb 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -422,6 +422,7 @@
                 || first.brightnessMaximum != second.brightnessMaximum
                 || first.brightnessDefault != second.brightnessDefault
                 || first.installOrientation != second.installOrientation
+                || first.isForceSdr != second.isForceSdr
                 || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
                 || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
                 || !first.thermalRefreshRateThrottling.contentEquals(
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index cc6904f..156d8a0 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -103,7 +103,7 @@
         final TaskDisplayArea displayArea = task.getDisplayArea();
         final Rect screenBounds = displayArea.getBounds();
         final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
-        if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
+        if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()) {
             return centerInScreen(idealSize, screenBounds);
         }
         if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index 61fbb96..da76317 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -35,8 +35,8 @@
             "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
 
     /** Whether desktop mode is enabled. */
-    static boolean isDesktopModeEnabled(@NonNull Context context) {
-        return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
+    static boolean isDesktopModeEnabled() {
+        return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isEnabled();
     }
 
     /**
@@ -60,7 +60,7 @@
      * Return {@code true} if desktop mode can be entered on the current device.
      */
     static boolean canEnterDesktopMode(@NonNull Context context) {
-        return isDesktopModeEnabled(context)
+        return isDesktopModeEnabled()
                 && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context));
     }
 }
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 5514294e..e007b1d 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -181,22 +181,30 @@
         return true;
     }
 
-    boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken,
+    boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken,
             @NonNull WindowState transferToHostWindowState) {
         EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken);
         if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
             return false;
         }
+        if (callingUid != ew.mOwnerUid) {
+            throw new SecurityException(
+                    "Transfer request must originate from owner of transferFromToken");
+        }
         return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
                 transferToHostWindowState.mInputChannelToken);
     }
 
-    boolean transferToEmbedded(WindowState hostWindowState,
+    boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
             @NonNull InputTransferToken transferToToken) {
         final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken);
         if (!isValidTouchGestureParams(hostWindowState, ew)) {
             return false;
         }
+        if (callingUid != hostWindowState.mOwnerUid) {
+            throw new SecurityException(
+                    "Transfer request must originate from owner of transferFromToken");
+        }
         return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
                 ew.getInputChannelToken());
     }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 638e92f..42ea5a8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -28,6 +28,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -57,6 +58,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 
 import java.io.PrintWriter;
@@ -1761,10 +1763,10 @@
      * @return last reparented root task, or {@code null} if the root tasks had to be destroyed.
      */
     Task remove() {
+        final TaskDisplayArea toDisplayArea = getReparentToTaskDisplayArea(getFocusedRootTask());
         mPreferredTopFocusableRootTask = null;
         // TODO(b/153090332): Allow setting content removal mode per task display area
         final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove();
-        final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         Task lastReparentedRootTask = null;
 
         // Root tasks could be reparented from the removed display area to other display area. After
@@ -1830,6 +1832,41 @@
         return lastReparentedRootTask;
     }
 
+    /**
+     * Returns the {@link TaskDisplayArea} to which root tasks should be reparented.
+     *
+     * <p>In the automotive multi-user multi-display environment where background users have
+     * UI access on their assigned displays (a.k.a. visible background users), it's not allowed to
+     * launch an activity on an unassigned display. If an activity is attempted to launch on an
+     * unassigned display, it throws an exception.
+     * <p>This method determines the appropriate {@link TaskDisplayArea} for reparenting root tasks
+     * when a display is removed, in order to avoid the exception. If the root task is null,
+     * the visible background user is not supported or the user associated with the root task is
+     * not a visible background user, it returns the default {@link TaskDisplayArea} of the default
+     * display. Otherwise, it returns the default {@link TaskDisplayArea} of the main display
+     * assigned to the user.
+     *
+     * @param rootTask The root task whose {@link TaskDisplayArea} needs to be determined.
+     * @return The {@link TaskDisplayArea} where the root tasks should be reparented to.
+     */
+    private TaskDisplayArea getReparentToTaskDisplayArea(Task rootTask) {
+        final TaskDisplayArea defaultTaskDisplayArea =
+                mRootWindowContainer.getDefaultTaskDisplayArea();
+        if (rootTask == null) {
+            return defaultTaskDisplayArea;
+        }
+        UserManagerInternal userManagerInternal = mAtmService.mWindowManager.mUmInternal;
+        if (!userManagerInternal.isVisibleBackgroundFullUser(rootTask.mUserId)) {
+            return defaultTaskDisplayArea;
+        }
+        int toDisplayId = userManagerInternal.getMainDisplayAssignedToUser(rootTask.mUserId);
+        if (toDisplayId == INVALID_DISPLAY) {
+            return defaultTaskDisplayArea;
+        }
+        DisplayContent dc = mRootWindowContainer.getDisplayContent(toDisplayId);
+        return dc != null ? dc.getDefaultTaskDisplayArea() : defaultTaskDisplayArea;
+    }
+
     /** Whether this task display area can request orientation. */
     boolean canSpecifyOrientation(@ScreenOrientation int orientation) {
         // Only allow to specify orientation if this TDA is the last focused one on this logical
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9d46529..7c3f0f2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -35,6 +35,7 @@
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -115,7 +116,6 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.AlwaysTruePredicate;
-import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -457,7 +457,7 @@
         source.setFrame(provider.getArbitraryRectangle())
                 .updateSideHint(getBounds())
                 .setBoundingRects(provider.getBoundingRects());
-        if (Flags.enableCaptionCompatInsetForceConsumption()) {
+        if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
             source.setFlags(provider.getFlags());
         }
         mLocalInsetsSources.put(id, source);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 33f2dd1..b8f47cc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9212,6 +9212,8 @@
         final InputApplicationHandle applicationHandle;
         final String name;
         Objects.requireNonNull(outInputChannel);
+        Objects.requireNonNull(inputTransferToken);
+
         synchronized (mGlobalLock) {
             WindowState hostWindowState = hostInputTransferToken != null
                     ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
@@ -9236,6 +9238,7 @@
         Objects.requireNonNull(transferFromToken);
         Objects.requireNonNull(transferToToken);
 
+        final int callingUid = Binder.getCallingUid();
         final long identity = Binder.clearCallingIdentity();
         boolean didTransfer;
         try {
@@ -9245,12 +9248,14 @@
                 // represents an embedded window so transfer from host to embedded.
                 WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
                 if (windowStateTo != null) {
-                    didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
+                    didTransfer = mEmbeddedWindowController.transferToHost(callingUid,
+                            transferFromToken,
                             windowStateTo);
                 } else {
                     WindowState windowStateFrom = mInputToWindowMap.get(
                             transferFromToken.getToken());
-                    didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
+                    didTransfer = mEmbeddedWindowController.transferToEmbedded(callingUid,
+                            windowStateFrom,
                             transferToToken);
                 }
             }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 976be4a..30d6f0a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -326,6 +326,7 @@
     public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
     public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23;
     public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24;
+    public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25;
     public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
 
     /**
@@ -1293,8 +1294,12 @@
         if (hasResumedFreeform
                 && com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
                 // Exclude task layer 1 because it is already the top most.
-                && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
-            stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+                && minTaskLayer > 1) {
+            if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+                stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+            } else {
+                stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
+            }
         }
         stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
         if (visible) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 8c4448e..155e73c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1808,9 +1808,30 @@
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
-    for (int32_t iconId = static_cast<int32_t>(PointerIconStyle::TYPE_CONTEXT_MENU);
-         iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_HANDWRITING); ++iconId) {
-        const PointerIconStyle pointerIconStyle = static_cast<PointerIconStyle>(iconId);
+    constexpr static std::array ADDITIONAL_STYLES{PointerIconStyle::TYPE_CONTEXT_MENU,
+                                                  PointerIconStyle::TYPE_HAND,
+                                                  PointerIconStyle::TYPE_HELP,
+                                                  PointerIconStyle::TYPE_WAIT,
+                                                  PointerIconStyle::TYPE_CELL,
+                                                  PointerIconStyle::TYPE_CROSSHAIR,
+                                                  PointerIconStyle::TYPE_TEXT,
+                                                  PointerIconStyle::TYPE_VERTICAL_TEXT,
+                                                  PointerIconStyle::TYPE_ALIAS,
+                                                  PointerIconStyle::TYPE_COPY,
+                                                  PointerIconStyle::TYPE_NO_DROP,
+                                                  PointerIconStyle::TYPE_ALL_SCROLL,
+                                                  PointerIconStyle::TYPE_HORIZONTAL_DOUBLE_ARROW,
+                                                  PointerIconStyle::TYPE_VERTICAL_DOUBLE_ARROW,
+                                                  PointerIconStyle::TYPE_TOP_RIGHT_DOUBLE_ARROW,
+                                                  PointerIconStyle::TYPE_TOP_LEFT_DOUBLE_ARROW,
+                                                  PointerIconStyle::TYPE_ZOOM_IN,
+                                                  PointerIconStyle::TYPE_ZOOM_OUT,
+                                                  PointerIconStyle::TYPE_GRAB,
+                                                  PointerIconStyle::TYPE_GRABBING,
+                                                  PointerIconStyle::TYPE_HANDWRITING,
+                                                  PointerIconStyle::TYPE_SPOT_HOVER};
+
+    for (const auto pointerIconStyle : ADDITIONAL_STYLES) {
         PointerIcon pointerIcon = loadPointerIcon(env, displayId, pointerIconStyle);
         (*outResources)[pointerIconStyle] = toSpriteIcon(pointerIcon);
         if (!pointerIcon.bitmapFrames.empty()) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ab459df..9fdf088 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -107,7 +107,7 @@
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogConfigurationService;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.EmergencyAffordanceManager;
@@ -1097,7 +1097,7 @@
         if (android.tracing.Flags.clientSideProtoLogging()) {
             t.traceBegin("StartProtoLogConfigurationService");
             ServiceManager.addService(
-                    Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService());
+                    Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationServiceImpl());
             t.traceEnd();
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 8b80f85..255dcb0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
@@ -195,8 +196,8 @@
     private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
     private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
     private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
     private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
             STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
 
@@ -238,6 +239,8 @@
 
     private UserManager mUserManager;
 
+    private int[] mAllowedHdrOutputTypes;
+
     private final DisplayManagerService.Injector mShortMockedInjector =
             new DisplayManagerService.Injector() {
                 @Override
@@ -256,11 +259,12 @@
                             displayAdapterListener, flags,
                             mMockedDisplayNotificationManager,
                             new LocalDisplayAdapter.Injector() {
-                        @Override
-                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-                            return mSurfaceControlProxy;
-                        }
-                    });
+                                @Override
+                                public LocalDisplayAdapter.SurfaceControlProxy
+                                        getSurfaceControlProxy() {
+                                    return mSurfaceControlProxy;
+                                }
+                            });
                 }
 
                 @Override
@@ -320,7 +324,7 @@
 
         @Override
         int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
-                int[] autoHdrTypes) {
+                int[] allowedHdrOutputTypes) {
             mHdrConversionMode = conversionMode;
             mPreferredHdrOutputType = preferredHdrOutputType;
             return Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -1295,11 +1299,11 @@
                         .setUniqueId("uniqueId --- mirror display");
         assertThrows(SecurityException.class, () -> {
             localService.createVirtualDisplay(
-                            builder.build(),
-                            mMockAppToken /* callback */,
-                            null /* virtualDeviceToken */,
-                            mock(DisplayWindowPolicyController.class),
-                            PACKAGE_NAME);
+                    builder.build(),
+                    mMockAppToken /* callback */,
+                    null /* virtualDeviceToken */,
+                    mock(DisplayWindowPolicyController.class),
+                    PACKAGE_NAME);
         });
     }
 
@@ -1433,7 +1437,7 @@
 
         // The virtual display should not have FLAG_ALWAYS_UNLOCKED set.
         assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
+                & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
     }
 
     /**
@@ -1466,7 +1470,7 @@
 
         // The virtual display should not have FLAG_PRESENTATION set.
         assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
-                        & DisplayDeviceInfo.FLAG_PRESENTATION));
+                & DisplayDeviceInfo.FLAG_PRESENTATION));
     }
 
     @Test
@@ -2358,6 +2362,7 @@
                 HdrConversionMode.HDR_CONVERSION_FORCE,
                 Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
         displayManager.setHdrConversionModeInternal(mode);
+
         assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
         assertEquals(mode.getConversionMode(), mHdrConversionMode);
         assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
@@ -2402,6 +2407,86 @@
     }
 
     @Test
+    public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() {
+        DisplayManagerService displayManager = new DisplayManagerService(
+                mContext, new BasicInjector() {
+                    @Override
+                    int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+                            int[] allowedTypes) {
+                        mAllowedHdrOutputTypes = allowedTypes;
+                        return Display.HdrCapabilities.HDR_TYPE_INVALID;
+                    }
+
+                    // Overriding this method to capture the allowed HDR type
+                    @Override
+                    int[] getSupportedHdrOutputTypes() {
+                        return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+                    }
+                });
+
+        // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion
+        displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+        displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+        displayManager.setHdrConversionModeInternal(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+
+        assertEquals(1, mAllowedHdrOutputTypes.length);
+        assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]);
+
+        // Action: disable Dolby Vision, set userDisabledTypes not allowed
+        displayManager.setUserDisabledHdrTypesInternal(
+                new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+        displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+
+        assertEquals(0, mAllowedHdrOutputTypes.length);
+    }
+
+    @Test
+    public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() {
+        DisplayManagerService displayManager = new DisplayManagerService(
+                mContext, new BasicInjector() {
+                    @Override
+                    int[] getSupportedHdrOutputTypes() {
+                        return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+                    }
+                });
+
+        displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+        displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+        int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+        assertEquals(1, enabledHdrOutputTypes.length);
+        assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+        displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+        enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+        assertEquals(1, enabledHdrOutputTypes.length);
+        assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+        displayManager.setUserDisabledHdrTypesInternal(
+                new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+        enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+        assertEquals(0, enabledHdrOutputTypes.length);
+    }
+
+    @Test
+    public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        LogicalDisplay logicalDisplay =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+
+        displayManager.setHdrConversionModeInternal(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID));
+        assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+
+        displayManager.setHdrConversionModeInternal(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+        assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+    }
+
+    @Test
     public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3505,7 +3590,7 @@
     }
 
     private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-                                                      Display.Mode[] modes) {
+            Display.Mode[] modes) {
         FakeDisplayDevice displayDevice = new FakeDisplayDevice();
         DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
         displayDeviceInfo.supportedModes = modes;
@@ -3761,9 +3846,9 @@
         public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) {
             for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) {
                 if (mode.matchesIfValid(
-                          preferredMode.getPhysicalWidth(),
-                          preferredMode.getPhysicalHeight(),
-                          preferredMode.getRefreshRate())) {
+                        preferredMode.getPhysicalWidth(),
+                        preferredMode.getPhysicalHeight(),
+                        preferredMode.getRefreshRate())) {
                     mPreferredMode = mode;
                     break;
                 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index 8b65337..32135f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -22,9 +22,10 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
@@ -36,6 +37,7 @@
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -137,9 +139,17 @@
 
         mSensitiveContentProtectionManagerService.mNotificationListener =
                 spy(mSensitiveContentProtectionManagerService.mNotificationListener);
-        doCallRealMethod()
-                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
-                .onListenerConnected();
+
+        // Unexpected NLS interactions when registered cause test flakes. For purposes of this test,
+        // the test will control any NLS calls.
+        try {
+            doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                    .registerAsSystemService(any(), any(), anyInt());
+            doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                    .unregisterAsSystemService();
+        } catch (RemoteException e) {
+            // Intra-process call, should never happen.
+        }
 
         // Setup RankingMap and two possilbe rankings
         when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 5ec5302..f6ad07d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -62,6 +62,7 @@
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
 import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
 import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
 import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -534,6 +535,14 @@
         updateOomAdj(app);
         assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
         assertEquals("perceptible-freeform-activity", app.mState.getAdjType());
+
+        doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE
+                | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE)
+                .when(wpc).getActivityStateFlags();
+        updateOomAdj(app);
+        assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+                SCHED_GROUP_FOREGROUND_WINDOW);
+        assertEquals("vis-multi-window-activity", app.mState.getAdjType());
     }
 
     @SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 15ae463..0b762df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -290,7 +290,7 @@
 
         final WallpaperData fallbackData = mService.mFallbackWallpaper;
         assertEquals("Fallback wallpaper component should be ImageWallpaper.",
-                sImageWallpaperComponentName, fallbackData.wallpaperComponent);
+                sImageWallpaperComponentName, fallbackData.getComponent());
 
         verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
         verifyDisplayData();
@@ -580,7 +580,7 @@
         final WallpaperData lastData = mService.mLastWallpaper;
         assertNotNull("Last wallpaper must not be null", lastData);
         assertEquals("Last wallpaper component must be equals.", expectedComponent,
-                lastData.wallpaperComponent);
+                lastData.getComponent());
         assertEquals("The user id in last wallpaper should be the last switched user",
                 lastUserId, lastData.userId);
         assertNotNull("Must exist user data connection on last wallpaper data",
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 44aa868..a55aa23 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -30,7 +30,6 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.system.Os;
@@ -41,8 +40,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.text.flags.Flags;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -1106,7 +1103,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1126,7 +1122,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1);
@@ -1146,7 +1141,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1166,7 +1160,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1186,7 +1179,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1206,7 +1198,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1226,7 +1217,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1246,7 +1236,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1266,7 +1255,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1286,7 +1274,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1);
@@ -1306,7 +1293,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1326,7 +1312,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf and bar.ttf
         installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1346,7 +1331,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1366,7 +1350,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1386,7 +1369,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1406,7 +1388,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1426,7 +1407,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1446,7 +1426,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1466,7 +1445,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1486,7 +1464,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1506,7 +1483,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1527,7 +1503,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
         // Install font families, foo.ttf, bar.ttf.
         installTestFontFamilies(1 /* version */);
@@ -1548,7 +1523,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1569,7 +1543,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
     public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
         // Install font file, foo.ttf
         installTestFontFile(1 /* numFonts */, 1 /* version */);
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 689b241..abc9ce3 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
@@ -50,11 +50,12 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
+import android.app.AppOpsManager;
 import android.app.KeyguardManager;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
@@ -72,6 +73,7 @@
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.testing.TestableContext;
 import android.view.ContentRecordingSession;
 import android.view.ContentRecordingSession.RecordContent;
 
@@ -99,13 +101,14 @@
 
 /**
  * Tests for the {@link MediaProjectionManagerService} class.
- *
+ * <p>
  * Build/Install/Run:
  * atest FrameworksServicesTests:MediaProjectionManagerServiceTest
  */
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
+@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"})
 public class MediaProjectionManagerServiceTest {
     private static final int UID = 10;
     private static final String PACKAGE_NAME = "test.package";
@@ -151,7 +154,10 @@
                 }
             };
 
-    private Context mContext;
+    @Rule
+    public final TestableContext mContext = spy(
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()));
+
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
     private ContentRecordingSession mWaitingDisplaySession =
@@ -169,6 +175,8 @@
     @Mock
     private KeyguardManager mKeyguardManager;
     @Mock
+    AppOpsManager mAppOpsManager;
+    @Mock
     private IMediaProjectionWatcherCallback mWatcherCallback;
     @Mock
     private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@@ -185,10 +193,9 @@
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
 
-        mContext = spy(new ContextWrapper(
-                InstrumentationRegistry.getInstrumentation().getTargetContext()));
-        doReturn(mPackageManager).when(mContext).getPackageManager();
-        doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE));
+        mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+        mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
+        mContext.setMockPackageManager(mPackageManager);
 
         mClock = new OffsettableClock.Stopped();
         mWaitingDisplaySession.setWaitingForConsent(true);
@@ -291,6 +298,27 @@
         assertThat(mService.getActiveProjectionInfo()).isNotNull();
     }
 
+    @SuppressLint("MissingPermission")
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testCreateProjection_keyguardLocked_AppOpMediaProjection()
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA),
+                eq(projection.uid), eq(projection.packageName));
+        doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+
+        doReturn(PackageManager.PERMISSION_DENIED).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 {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index 4d2396c..65b4ac1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -104,6 +104,12 @@
     }
 
     @Test
+    public void createVibrationEffectFromSoundUri_opaqueUri() {
+        Uri uri = Uri.parse("a:b#c");
+        assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
+    }
+
+    @Test
     public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() {
         Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI;
         assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 41223db..2ef0573 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5038,7 +5038,7 @@
          * {@code true} - Enable NI SUPL message injection.
          */
         @FlaggedApi(android.location.flags.Flags
-                .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG)
+                .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG_BUGFIX)
         public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL =
                 KEY_PREFIX + "enable_ni_supl_message_injection_bool";
 
@@ -5059,7 +5059,7 @@
             defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
                     SUPL_EMERGENCY_MODE_TYPE_CP_ONLY);
             defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null);
-            if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+            if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
                 defaults.putBoolean(KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL, false);
             }
             return defaults;
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index c2e71f8..2a82d5f 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -21,6 +21,8 @@
 import android.content.Context
 import android.content.ContextWrapper
 import android.content.PermissionChecker
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
 import android.hardware.display.DisplayManager
 import android.hardware.display.DisplayViewport
 import android.hardware.display.VirtualDisplay
@@ -29,10 +31,13 @@
 import android.os.InputEventInjectionSync
 import android.os.SystemClock
 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.provider.Settings
 import android.view.View.OnKeyListener
 import android.view.InputDevice
+import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import android.view.SurfaceHolder
 import android.view.SurfaceView
@@ -94,6 +99,10 @@
         ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
             .mockStatic(PermissionChecker::class.java).build()!!
 
+    @JvmField
+    @Rule
+    val setFlagsRule = SetFlagsRule()
+
     @get:Rule
     val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
 
@@ -107,8 +116,14 @@
     private lateinit var windowManagerInternal: WindowManagerInternal
 
     @Mock
+    private lateinit var packageManagerInternal: PackageManagerInternal
+
+    @Mock
     private lateinit var uEventManager: UEventManager
 
+    @Mock
+    private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
+
     private lateinit var service: InputManagerService
     private lateinit var localService: InputManagerInternal
     private lateinit var context: Context
@@ -135,15 +150,29 @@
                 override fun registerLocalService(service: InputManagerInternal?) {
                     localService = service!!
                 }
+
+                override fun getKeyboardBacklightController(
+                    nativeService: NativeInputManagerService?,
+                    dataStore: PersistentDataStore?
+                ): InputManagerService.KeyboardBacklightControllerInterface {
+                    return kbdController
+                }
             })
         inputManagerGlobalSession = InputManagerGlobal.createTestSession(service)
         val inputManager = InputManager(context)
         whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
         whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)
+        whenever(context.checkCallingOrSelfPermission(Manifest.permission.MANAGE_KEY_GESTURES))
+            .thenReturn(
+                PackageManager.PERMISSION_GRANTED
+            )
 
         ExtendedMockito.doReturn(windowManagerInternal).`when` {
             LocalServices.getService(eq(WindowManagerInternal::class.java))
         }
+        ExtendedMockito.doReturn(packageManagerInternal).`when` {
+            LocalServices.getService(eq(PackageManagerInternal::class.java))
+        }
 
         assertTrue("Local service must be registered", this::localService.isInitialized)
         service.setWindowManagerCallbacks(wmCallbacks)
@@ -442,6 +471,37 @@
         verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
     }
 
+    @Test
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun handleKeyGestures_keyboardBacklight() {
+        service.systemRunning()
+
+        val backlightDownEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN)
+        service.interceptKeyBeforeDispatching(null, backlightDownEvent, /* policyFlags = */0)
+        verify(kbdController).decrementKeyboardBacklight(anyInt())
+
+        val backlightUpEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP)
+        service.interceptKeyBeforeDispatching(null, backlightUpEvent, /* policyFlags = */0)
+        verify(kbdController).incrementKeyboardBacklight(anyInt())
+    }
+
+    @Test
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun handleKeyGestures_toggleCapsLock() {
+        service.systemRunning()
+
+        val metaDownEvent = createKeyEvent(KeyEvent.KEYCODE_META_LEFT)
+        service.interceptKeyBeforeDispatching(null, metaDownEvent, /* policyFlags = */0)
+        val altDownEvent =
+            createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_DOWN)
+        service.interceptKeyBeforeDispatching(null, altDownEvent, /* policyFlags = */0)
+        val altUpEvent =
+            createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_UP)
+        service.interceptKeyBeforeDispatching(null, altUpEvent, /* policyFlags = */0)
+
+        verify(native).toggleCapsLock(anyInt())
+    }
+
     fun overrideSendActionKeyEventsToFocusedWindow(
         hasPermission: Boolean,
         hasPrivateFlag: Boolean
@@ -476,6 +536,25 @@
         )
         whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
     }
+
+    private fun createKeyEvent(
+        keycode: Int,
+        modifierState: Int = 0,
+        action: Int = KeyEvent.ACTION_DOWN
+    ): KeyEvent {
+        return KeyEvent(
+            /* downTime = */0,
+            /* eventTime = */0,
+            action,
+            keycode,
+            /* repeat = */0,
+            modifierState,
+            KeyCharacterMap.VIRTUAL_KEYBOARD,
+            /* scancode = */0,
+            /* flags = */0,
+            InputDevice.SOURCE_KEYBOARD
+        )
+    }
 }
 
 private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 681b7f2..b3a998e 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.input.debug;
 
+import static android.view.InputDevice.SOURCE_MOUSE;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 
 import static org.junit.Assert.assertEquals;
@@ -29,7 +30,9 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
+import android.hardware.input.InputManager;
 import android.testing.TestableContext;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -60,13 +63,15 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class TouchpadDebugViewTest {
-    private static final int TOUCHPAD_DEVICE_ID = 6;
+    private static final int TOUCHPAD_DEVICE_ID = 60;
 
     private TouchpadDebugView mTouchpadDebugView;
     private WindowManager.LayoutParams mWindowLayoutParams;
 
     @Mock
     WindowManager mWindowManager;
+    @Mock
+    InputManager mInputManager;
 
     Rect mWindowBounds;
     WindowMetrics mWindowMetrics;
@@ -79,12 +84,21 @@
         mTestableContext = new TestableContext(context);
 
         mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
+        mTestableContext.addMockSystemService(InputManager.class, mInputManager);
 
         mWindowBounds = new Rect(0, 0, 2560, 1600);
         mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f);
 
         when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
 
+        InputDevice inputDevice = new InputDevice.Builder()
+                .setId(TOUCHPAD_DEVICE_ID)
+                .setSources(InputDevice.SOURCE_TOUCHPAD | SOURCE_MOUSE)
+                .setName("Test Device " + TOUCHPAD_DEVICE_ID)
+                .build();
+
+        when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
+
         mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
                 new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
                         500f, 45f, 47f, -4f, 5f, (short) 10, true,
@@ -341,4 +355,43 @@
         mTouchpadDebugView.updateGestureInfo(gestureType, TOUCHPAD_DEVICE_ID);
         assertEquals(child.getText().toString(), TouchpadDebugView.getGestureText(gestureType));
     }
-}
+
+    @Test
+    public void testTwoFingerDrag() {
+        float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+        float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+        // Simulate ACTION_DOWN event (gesture starts).
+        MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(40f)
+                        .y(40f)
+                )
+                .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+        // Simulate ACTION_MOVE event (dragging with two fingers, processed as one pointer).
+        MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(40f + offsetX)
+                        .y(40f + offsetY)
+                )
+                .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+        // Simulate ACTION_UP event (gesture ends).
+        MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(40f + offsetX)
+                        .y(40f + offsetY)
+                )
+                .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+        // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
+        verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+    }
+}
\ No newline at end of file
diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp
index 5a7f12f..90998e6 100644
--- a/tests/Tracing/Android.bp
+++ b/tests/Tracing/Android.bp
@@ -15,7 +15,7 @@
     },
     // Include some source files directly to be able to access package members
     srcs: ["src/**/*.java"],
-    libs: ["android.test.runner"],
+    libs: ["android.test.runner.stubs.system"],
     static_libs: [
         "junit",
         "androidx.test.rules",
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index e841d9e..c882b4e 100644
--- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -42,7 +42,7 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer;
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogDataType;
 import com.android.internal.protolog.common.LogLevel;
@@ -166,7 +166,8 @@
                 return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
             });
         };
-        sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer);
+        sProtoLogConfigurationService =
+                new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer);
 
         if (android.tracing.Flags.clientSideProtoLogging()) {
             sProtoLog = new PerfettoProtoLogImpl(
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index e1bdd77..a3d03a8 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -150,11 +150,11 @@
 
     @Test
     public void canRegisterClientWithGroupsOnly() throws RemoteException {
-        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
 
-        final ProtoLogConfigurationService.RegisterClientArgs args =
-                new ProtoLogConfigurationService.RegisterClientArgs()
-                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+                new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(TEST_GROUP, true));
         service.registerClient(mMockClient, args);
 
@@ -165,11 +165,11 @@
     @Test
     public void willDumpViewerConfigOnlyOnceOnTraceStop()
             throws RemoteException, InvalidProtocolBufferException {
-        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
 
-        final ProtoLogConfigurationService.RegisterClientArgs args =
-                new ProtoLogConfigurationService.RegisterClientArgs()
-                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+                new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(TEST_GROUP, true))
                         .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
         service.registerClient(mMockClient, args);
@@ -200,13 +200,13 @@
     @Test
     public void willDumpViewerConfigOnLastClientDisconnected()
             throws RemoteException, FileNotFoundException {
-        final ProtoLogConfigurationService.ViewerConfigFileTracer tracer =
-                Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class);
-        final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer);
+        final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer =
+                Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class);
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer);
 
-        final ProtoLogConfigurationService.RegisterClientArgs args =
-                new ProtoLogConfigurationService.RegisterClientArgs()
-                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+                new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(TEST_GROUP, true))
                         .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
         service.registerClient(mMockClient, args);
@@ -225,10 +225,10 @@
 
     @Test
     public void sendEnableLoggingToLogcatToClient() throws RemoteException {
-        final var service = new ProtoLogConfigurationService();
+        final var service = new ProtoLogConfigurationServiceImpl();
 
-        final var args = new ProtoLogConfigurationService.RegisterClientArgs()
-                .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                         .GroupConfig(TEST_GROUP, false));
         service.registerClient(mMockClient, args);
 
@@ -242,11 +242,11 @@
 
     @Test
     public void sendDisableLoggingToLogcatToClient() throws RemoteException {
-        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
 
-        final ProtoLogConfigurationService.RegisterClientArgs args =
-                new ProtoLogConfigurationService.RegisterClientArgs()
-                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+                new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(TEST_GROUP, true));
         service.registerClient(mMockClient, args);
 
@@ -260,11 +260,11 @@
 
     @Test
     public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
-        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
 
-        final ProtoLogConfigurationService.RegisterClientArgs args =
-                new ProtoLogConfigurationService.RegisterClientArgs()
-                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+                new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(TEST_GROUP, false));
         service.registerClient(mMockClient, args);
 
@@ -277,15 +277,15 @@
 
     @Test
     public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
-        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
 
         Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
         service.enableProtoLogToLogcat(TEST_GROUP);
         Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
 
-        final ProtoLogConfigurationService.RegisterClientArgs args =
-                new ProtoLogConfigurationService.RegisterClientArgs()
-                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+        final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+                new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                                 .GroupConfig(TEST_GROUP, false));
         service.registerClient(mMockClient, args);
 
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index 290e7be..9467434 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -22,6 +22,7 @@
 import com.google.android.lint.aidl.EnforcePermissionDetector
 import com.google.android.lint.aidl.PermissionAnnotationDetector
 import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
+import com.google.android.lint.aidl.SimpleRequiresNoPermissionDetector
 import com.google.auto.service.AutoService
 
 @AutoService(IssueRegistry::class)
@@ -34,6 +35,7 @@
             EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION,
             PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
             SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
+            SimpleRequiresNoPermissionDetector.ISSUE_SIMPLE_REQUIRES_NO_PERMISSION,
     )
 
     override val api: Int
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt
new file mode 100644
index 0000000..1a13c02
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UastCallKind
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * Ensures all AIDL implementations hosted by system_server which don't call other methods are
+ * annotated with @RequiresNoPermission. AIDL Interfaces part of `exemptAidlInterfaces` are skipped
+ * during this search to ensure the detector targets only new AIDL Interfaces.
+ */
+class SimpleRequiresNoPermissionDetector : AidlImplementationDetector() {
+    override fun visitAidlMethod(
+        context: JavaContext,
+        node: UMethod,
+        interfaceName: String,
+        body: UBlockExpression
+    ) {
+        if (!isSystemServicePath(context)) return
+        if (context.evaluator.isAbstract(node)) return
+
+        val fullyQualifiedInterfaceName =
+            getContainingAidlInterfaceQualified(context, node) ?: return
+        if (exemptAidlInterfaces.contains(fullyQualifiedInterfaceName)) return
+
+        if (node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION)) return
+
+        if (!isCallingMethod(node)) {
+            context.report(
+                ISSUE_SIMPLE_REQUIRES_NO_PERMISSION,
+                node,
+                context.getLocation(node),
+                """
+                    Method ${node.name} doesn't perform any permission checks, meaning it should \
+                    be annotated with @RequiresNoPermission.
+                """.trimMargin()
+            )
+        }
+    }
+
+    private fun isCallingMethod(node: UMethod): Boolean {
+        val uCallExpressionVisitor = UCallExpressionVisitor()
+        node.accept(uCallExpressionVisitor)
+
+        return uCallExpressionVisitor.isCallingMethod
+    }
+
+    /**
+     * Visits the body of a `UMethod` and determines if it encounters a `UCallExpression` which is
+     * a `UastCallKind.METHOD_CALL`. `isCallingMethod` will hold the result of the search procedure.
+     */
+    private class UCallExpressionVisitor : AbstractUastVisitor() {
+        var isCallingMethod = false
+
+        override fun visitElement(node: UElement): Boolean {
+            // Stop the search early when a method call has been found.
+            return isCallingMethod
+        }
+
+        override fun visitCallExpression(node: UCallExpression): Boolean {
+            if (node.kind != UastCallKind.METHOD_CALL) return false
+
+            isCallingMethod = true
+            return true
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION = """
+            Method implementations of AIDL Interfaces hosted by the `system_server` which do not
+            call any other methods should be annotated with @RequiresNoPermission. That is because
+            not calling any other methods implies that the method does not perform any permission
+            checking.
+
+            Please migrate to an @RequiresNoPermission annotation.
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_SIMPLE_REQUIRES_NO_PERMISSION = Issue.create(
+            id = "SimpleRequiresNoPermission",
+            briefDescription = "System Service APIs not calling other methods should use @RNP",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                SimpleRequiresNoPermissionDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+        )
+    }
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt
index 92d0829..824be93 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt
@@ -17,7 +17,6 @@
 package com.google.android.lint.aidl
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -64,7 +63,7 @@
                     """
                         package com.android.server;
                         public class Bar extends IBar.Stub {
-                            public void testMethod() { }
+                            public void testMethod(int parameter1, int parameter2) { }
                         }
                     """
                 )
@@ -75,8 +74,8 @@
             .expect(
                 """
                 src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation]
-                    public void testMethod() { }
-                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    public void testMethod(int parameter1, int parameter2) { }
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 1 errors, 0 warnings
                 """
             )
@@ -90,7 +89,7 @@
                     """
                         package com.android.server;
                         public class Bar extends IBar.Stub {
-                            public void testMethod() { }
+                            public void testMethod(int parameter1, int parameter2) { }
                         }
                     """
                 )
@@ -132,7 +131,7 @@
                     """
                         package com.android.server;
                         public abstract class Bar extends IBar.Stub {
-                            public abstract void testMethod();
+                            public abstract void testMethod(int parameter1, int parameter2);
                         }
                     """
                 )
@@ -177,50 +176,6 @@
             .expectClean()
     }
 
-    /* Stubs */
-
-    // A service with permission annotation on the method.
-    private val interfaceIFoo: TestFile = java(
-        """
-        public interface IFoo extends android.os.IInterface {
-         public static abstract class Stub extends android.os.Binder implements IFoo {
-          }
-          @Override
-          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-          public void testMethod();
-          @Override
-          @android.annotation.RequiresNoPermission
-          public void testMethodNoPermission();
-          @Override
-          @android.annotation.PermissionManuallyEnforced
-          public void testMethodManual();
-        }
-        """
-    ).indented()
-
-    // A service with no permission annotation.
-    private val interfaceIBar: TestFile = java(
-        """
-        public interface IBar extends android.os.IInterface {
-         public static abstract class Stub extends android.os.Binder implements IBar {
-          }
-          public void testMethod();
-        }
-        """
-    ).indented()
-
-    // A service whose AIDL Interface is exempted.
-    private val interfaceIExempted: TestFile = java(
-        """
-        package android.accessibilityservice;
-        public interface IBrailleDisplayConnection extends android.os.IInterface {
-         public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection {
-          }
-          public void testMethod();
-        }
-        """
-    ).indented()
-
     private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted)
 
     private fun createVisitedPath(filename: String) =
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt
new file mode 100644
index 0000000..a33b48c
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt
@@ -0,0 +1,244 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+class SimpleRequiresNoPermissionDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = SimpleRequiresNoPermissionDetector()
+    override fun getIssues(): List<Issue> = listOf(
+        SimpleRequiresNoPermissionDetector
+            .ISSUE_SIMPLE_REQUIRES_NO_PERMISSION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testRequiresNoPermissionUsedCorrectly_shouldNotWarn() {
+        lint()
+            .files(
+                java(
+                    createVisitedPath("Foo.java"),
+                    """
+                        package com.android.server;
+                        public class Foo extends IFoo.Stub {
+                            private int memberInt;
+
+                            @Override
+                            @android.annotation.RequiresNoPermission
+                            public void testMethodNoPermission(int parameter1, int parameter2) {
+                                if (parameter1 < parameter2) {
+                                    memberInt = parameter1;
+                                } else {
+                                    memberInt = parameter2;
+                                }
+                            }
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expectClean()
+    }
+
+    fun testMissingRequiresNoPermission_shouldWarn() {
+        lint()
+            .files(
+                java(
+                    createVisitedPath("Bar.java"),
+                    """
+                        package com.android.server;
+                        public class Bar extends IBar.Stub {
+                            private int memberInt;
+
+                            @Override
+                            public void testMethod(int parameter1, int parameter2) {
+                                if (parameter1 < parameter2) {
+                                    memberInt = parameter1;
+                                } else {
+                                    memberInt = parameter2;
+                                }
+                            }
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expect(
+                """
+                src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testMethodOnlyPerformsConstructorCall_shouldWarn() {
+        lint()
+            .files(
+                java(
+                    createVisitedPath("Bar.java"),
+                    """
+                        package com.android.server;
+                        public class Bar extends IBar.Stub {
+                            private IntPair memberIntPair;
+
+                            @Override
+                            public void testMethod(int parameter1, int parameter2) {
+                                memberIntPair = new IntPair(parameter1, parameter2);
+                            }
+
+                            private static class IntPair {
+                                public int first;
+                                public int second;
+
+                                public IntPair(int first, int second) {
+                                    this.first = first;
+                                    this.second = second;
+                                }
+                            }
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expect(
+                """
+                src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testMissingRequiresNoPermissionInIgnoredDirectory_shouldNotWarn() {
+        lint()
+            .files(
+                java(
+                    ignoredPath,
+                    """
+                        package com.android.server;
+                        public class Bar extends IBar.Stub {
+                            @Override
+                            public void testMethod(int parameter1, int parameter2) {}
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expectClean()
+    }
+
+    fun testMissingRequiresNoPermissionAbstractMethod_shouldNotWarn() {
+        lint()
+            .files(
+                java(
+                    createVisitedPath("Bar.java"),
+                    """
+                        package com.android.server;
+                        public abstract class Bar extends IBar.Stub {
+                            private int memberInt;
+
+                            @Override
+                            public abstract void testMethodNoPermission(int parameter1, int parameter2);
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expectClean()
+    }
+
+    // If this test fails, consider the following steps:
+    //   1. Pick the first entry (interface) from `exemptAidlInterfaces`.
+    //   2. Change `interfaceIExempted` to use that interface.
+    //   3. Change this test's class to extend the interface's Stub.
+    fun testMissingRequiresNoPermissionAidlInterfaceExempted_shouldNotWarn() {
+        lint()
+            .files(
+                java(
+                    createVisitedPath("Bar.java"),
+                    """
+                        package com.android.server;
+                        public class Bar extends android.accessibilityservice.IBrailleDisplayConnection.Stub {
+                            public void testMethod(int parameter1, int parameter2) {}
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expectClean()
+    }
+
+    fun testMethodMakesAnotherMethodCall_shouldNotWarn() {
+        lint()
+            .files(
+                java(
+                    createVisitedPath("Bar.java"),
+                    """
+                        package com.android.server;
+                        public class Bar extends IBar.Stub {
+                            private int memberInt;
+
+                            @Override
+                            public void testMethod(int parameter1, int parameter2) {
+                                if (!hasPermission()) return;
+
+                                if (parameter1 < parameter2) {
+                                    memberInt = parameter1;
+                                } else {
+                                    memberInt = parameter2;
+                                }
+                            }
+
+                            private bool hasPermission() {
+                                // Perform a permission check.
+                                return true;
+                            }
+                        }
+                    """
+                )
+                    .indented(),
+                *stubs
+            )
+            .run()
+            .expectClean()
+    }
+
+    private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted)
+
+    private fun createVisitedPath(filename: String) =
+        "src/frameworks/base/services/java/com/android/server/$filename"
+
+    private val ignoredPath = "src/test/pkg/TestClass.java"
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
index 2ec8fdd..18a8f18 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -85,4 +85,46 @@
         }
     }
     """.trimIndent()
-)
\ No newline at end of file
+)
+
+// A service with permission annotation on the method.
+val interfaceIFoo: TestFile = java(
+    """
+        public interface IFoo extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IFoo {
+          }
+          @Override
+          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+          public void testMethod();
+          @Override
+          @android.annotation.RequiresNoPermission
+          public void testMethodNoPermission(int parameter1, int parameter2);
+          @Override
+          @android.annotation.PermissionManuallyEnforced
+          public void testMethodManual();
+        }
+        """
+).indented()
+
+// A service with no permission annotation.
+val interfaceIBar: TestFile = java(
+    """
+        public interface IBar extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IBar {
+          }
+          public void testMethod(int parameter1, int parameter2);
+        }
+        """
+).indented()
+
+// A service whose AIDL Interface is exempted.
+val interfaceIExempted: TestFile = java(
+    """
+        package android.accessibilityservice;
+        public interface IBrailleDisplayConnection extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection {
+          }
+          public void testMethod();
+        }
+        """
+).indented()