Merge changes from topic "revert-27057737-VHSLSLTGYG" into main

* changes:
  Revert "Flag off all edges leading KTF out of the Lockscreen scene"
  Revert "Fix WindowManagerLockscreenVisibility"
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ccdf37..ab5d503 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1043,20 +1043,12 @@
     name: "device_policy_aconfig_flags",
     package: "android.app.admin.flags",
     container: "system",
-    exportable: true,
     srcs: [
         "core/java/android/app/admin/flags/flags.aconfig",
     ],
 }
 
 java_aconfig_library {
-    name: "device_policy_exported_aconfig_flags_lib",
-    aconfig_declarations: "device_policy_aconfig_flags",
-    defaults: ["framework-minus-apex-aconfig-java-defaults"],
-    mode: "exported",
-}
-
-java_aconfig_library {
     name: "device_policy_aconfig_flags_lib",
     aconfig_declarations: "device_policy_aconfig_flags",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 02e8eec..e680103 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -262,6 +262,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 255ec92..74b34fb 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -246,12 +246,20 @@
     visibility: ["//visibility:private"],
 }
 
+java_genrule {
+    name: "z00-all-updatable-modules-system-stubs",
+    cmd: "cp $(in) $(out)",
+    srcs: [":all-updatable-modules-system-stubs"],
+    out: ["z00-all-updatable-modules-system-stubs.jar"],
+    visibility: ["//visibility:private"],
+}
+
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     libs: [
         "100-framework-minus-apex.ravenwood",
         "200-kxml2-android",
-        "all-updatable-modules-system-stubs",
+
         "android.test.mock.ravenwood",
         "ravenwood-helper-runtime",
         "hoststubgen-helper-runtime.ravenwood",
@@ -267,6 +275,9 @@
         "ravenwood-junit-impl-flag",
         "mockito-ravenwood-prebuilt",
         "inline-mockito-ravenwood-prebuilt",
+
+        // It's a stub, so it should be towards the end.
+        "z00-all-updatable-modules-system-stubs",
     ],
     jni_libs: [
         "libandroid_runtime",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index a4a2e80..9b0f5c9 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -133,7 +133,7 @@
         pw.println("      --tag: Tag of the blob to delete.");
         pw.println("idle-maintenance");
         pw.println("    Run idle maintenance which takes care of removing stale data.");
-        pw.println("query-blob-existence [-b BLOB_ID]");
+        pw.println("query-blob-existence [-b BLOB_ID] [-u | --user USER_ID]");
         pw.println("    Prints 1 if blob exists, otherwise 0.");
         pw.println();
     }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2437be8..e225c5b0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -966,7 +966,6 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
-    ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
     ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4ac6bac..0caea7f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -114,6 +114,7 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.util.NewlineNormalizer;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -3190,7 +3191,7 @@
             return charSequence;
         }
 
-        return charSequence.toString().replaceAll("[\r\n]+", "\n");
+        return NewlineNormalizer.normalizeNewlines(charSequence.toString());
     }
 
     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -6594,6 +6595,11 @@
          * @hide
          */
         public RemoteViews createCompactHeadsUpContentView() {
+            // Don't show compact heads up for FSI notifications.
+            if (mN.fullScreenIntent != null) {
+                return createHeadsUpContentView(/* increasedHeight= */ false);
+            }
+
             if (mStyle != null) {
                 final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView();
                 if (styleView != null) {
@@ -10351,7 +10357,7 @@
         @Nullable
         @Override
         public RemoteViews makeCompactHeadsUpContentView() {
-            // TODO(b/336228700): Apply minimal HUN treatment for Call Style.
+            // Use existing heads up for call style.
             return makeHeadsUpContentView(false);
         }
 
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index efd5a45..ef8501f 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -351,6 +351,12 @@
     }
 
     /** @hide */
+    public boolean isFreeform() {
+        return configuration.windowConfiguration.getWindowingMode()
+                == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+    }
+
+    /** @hide */
     @WindowConfiguration.ActivityType
     public int getActivityType() {
         return configuration.windowConfiguration.getActivityType();
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 46c9e78..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -21,7 +21,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.app.admin.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -177,10 +176,6 @@
      * provisioned into "affiliated" mode when on a Headless System User Mode device.
      *
      * <p>This mode adds a Profile Owner to all users other than the user the Device Owner is on.
-     *
-     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
-     * DPCs should set the value of attribute "headless-device-owner-mode" inside the
-     * "headless-system-user" tag as "affiliated".
      */
     public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
 
@@ -190,10 +185,6 @@
      *
      * <p>This mode only allows a single secondary user on the device blocking the creation of
      * additional secondary users.
-     *
-     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
-     * DPCs should set the value of attribute "headless-device-owner-mode" inside the
-     * "headless-system-user" tag as "single_user".
      */
     @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
@@ -392,30 +383,17 @@
                     }
                     mSupportsTransferOwnership = true;
                 } else if (tagName.equals("headless-system-user")) {
-                    String deviceOwnerModeStringValue = null;
-                    if (Flags.headlessSingleUserCompatibilityFix()) {
-                        deviceOwnerModeStringValue = parser.getAttributeValue(
-                                 null, "headless-device-owner-mode");
-                    }
-                    if (deviceOwnerModeStringValue == null) {
-                        deviceOwnerModeStringValue =
-                                parser.getAttributeValue(null, "device-owner-mode");
-                    }
+                    String deviceOwnerModeStringValue =
+                            parser.getAttributeValue(null, "device-owner-mode");
 
-                    if ("unsupported".equalsIgnoreCase(deviceOwnerModeStringValue)) {
+                    if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
-                    } else if ("affiliated".equalsIgnoreCase(deviceOwnerModeStringValue)) {
+                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
-                    } else if ("single_user".equalsIgnoreCase(deviceOwnerModeStringValue)) {
+                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
                     } else {
-                        if (Flags.headlessSingleUserCompatibilityFix()) {
-                            Log.e(TAG, "Unknown headless-system-user mode: "
-                                    + deviceOwnerModeStringValue);
-                        } else {
-                            throw new XmlPullParserException(
-                                    "headless-system-user mode must be valid");
-                        }
+                        throw new XmlPullParserException("headless-system-user mode must be valid");
                     }
                 }
             }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 83daa45..6da96c1 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -217,6 +217,16 @@
 }
 
 flag {
+  name: "disallow_user_control_stopped_state_fix"
+  namespace: "enterprise"
+  description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app"
+  bug: "330688482"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "esim_management_ux_enabled"
   namespace: "enterprise"
   description: "Enable UX changes for esim management"
@@ -303,24 +313,3 @@
       purpose: PURPOSE_BUGFIX
     }
 }
-
-flag {
-    name: "headless_single_user_compatibility_fix"
-    namespace: "enterprise"
-    description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds"
-    bug: "338050276"
-    is_exported: true
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
-    name: "headless_single_min_target_sdk"
-    namespace: "enterprise"
-    description: "Only allow DPCs targeting Android V to provision into single user mode"
-    bug: "338588825"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
-}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b070742..37f419d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -162,17 +162,6 @@
 
     /** @hide */
     @TestApi
-    @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR)
-    public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag, @NonNull IBinder token,
-            @Nullable String[] renouncedPermissions,
-            @Nullable AttributionSource next) {
-        this(uid, pid, packageName, attributionTag, token, renouncedPermissions,
-                Context.DEVICE_ID_DEFAULT, next);
-    }
-
-    /** @hide */
-    @TestApi
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
@@ -473,6 +462,20 @@
     }
 
     /**
+     * @return The next package's device Id from its context.
+     * This device ID is used for permissions checking during attribution source validation.
+     *
+     * @hide
+     */
+    public int getNextDeviceId() {
+        if (mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
+            return mAttributionSourceState.next[0].deviceId;
+        }
+        return Context.DEVICE_ID_DEFAULT;
+    }
+
+    /**
      * Checks whether this attribution source can be trusted. That is whether
      * the app it refers to created it and provided to the attribution chain.
      *
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index ac043d3..91b05c2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -1353,9 +1353,6 @@
     /**
      * Get a snapshot of the real-time status of the devices on the CEC bus.
      *
-     * <p>This only applies to devices with switch functionality, which are devices with one
-     * or more than one HDMI inputs.
-     *
      * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An
      * empty list will be returned if there is none.
      */
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 243ae14..8f78032 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -148,8 +148,6 @@
 
     IInputDeviceBatteryState getBatteryState(int deviceId);
 
-    void setPointerIconType(int typeId);
-    void setCustomPointerIcon(in PointerIcon icon);
     boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId,
             in IBinder inputToken);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dd4ea31..57004bc 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -992,21 +992,14 @@
     }
 
     /**
-     * Changes the mouse pointer's icon shape into the specified id.
+     * This method exists for backwards-compatibility, and is a no-op.
      *
-     * @param iconId The id of the pointer graphic, as a value between
-     * {@link PointerIcon#TYPE_ARROW} and {@link PointerIcon#TYPE_HANDWRITING}.
-     *
+     * @deprecated
      * @hide
      */
     @UnsupportedAppUsage
     public void setPointerIconType(int iconId) {
-        mGlobal.setPointerIconType(iconId);
-    }
-
-    /** @hide */
-    public void setCustomPointerIcon(PointerIcon icon) {
-        mGlobal.setCustomPointerIcon(icon);
+        Log.e(TAG, "setPointerIcon: Unsupported app usage!");
     }
 
     /** @hide */
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index a9c97b1..cb3af2b 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1411,28 +1411,6 @@
     }
 
     /**
-     * @see InputManager#setPointerIconType(int)
-     */
-    public void setPointerIconType(int iconId) {
-        try {
-            mIm.setPointerIconType(iconId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @see InputManager#setCustomPointerIcon(PointerIcon)
-     */
-    public void setCustomPointerIcon(PointerIcon icon) {
-        try {
-            mIm.setCustomPointerIcon(icon);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder)
      */
     public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index b588308..0e28560 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -44,14 +44,6 @@
 }
 
 flag {
-  name: "attribution_source_constructor"
-  is_exported: true
-  namespace: "permissions"
-  description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)"
-  bug: "304478648"
-}
-
-flag {
     name: "enhanced_confirmation_mode_apis_enabled"
     is_exported: true
     is_fixed_read_only: true
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e6ddf35..009713f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5123,13 +5123,6 @@
         public static final String SCREEN_BRIGHTNESS = "screen_brightness";
 
         /**
-         * The screen backlight brightness between 0.0f and 1.0f.
-         * @hide
-         */
-        @Readable
-        public static final String SCREEN_BRIGHTNESS_FLOAT = "screen_brightness_float";
-
-        /**
          * Control whether to enable automatic brightness mode.
          */
         @Readable
@@ -6273,7 +6266,6 @@
             PUBLIC_SETTINGS.add(DIM_SCREEN);
             PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
-            PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FLOAT);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
             PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
             PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 51758aa..ee5e533 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -81,8 +81,15 @@
 }
 
 flag {
-  name: "report_primary_auth_attempts"
-  namespace: "biometrics"
-  description: "Report primary auth attempts from LockSettingsService"
-  bug: "285053096"
+    name: "report_primary_auth_attempts"
+    namespace: "biometrics"
+    description: "Report primary auth attempts from LockSettingsService"
+    bug: "285053096"
+}
+
+flag {
+    name: "dump_attestation_verifications"
+    namespace: "hardware_backed_security"
+    description: "Add a dump capability for attestation_verification service"
+    bug: "335498868"
 }
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index db665a9..c4becea 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -1392,10 +1392,6 @@
 
     private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets,
             Rect[] bounds) {
-        if (displayW == displayH) {
-            throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
-                    + displayH + " bounding rects=" + Arrays.toString(bounds));
-        }
 
         int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide(
                 displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT));
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 498be2f..3743035 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -108,11 +108,6 @@
     void dispatchDragEvent(in DragEvent event);
 
     /**
-     * Pointer icon events
-     */
-    void updatePointerIcon(float x, float y);
-
-    /**
      * Called for non-application windows when the enter animation has completed.
      */
     void dispatchWindowShown();
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 86264eb..e3e4fc0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -288,8 +288,6 @@
 
     oneway void finishMovingTask(IWindow window);
 
-    oneway void updatePointerIcon(IWindow window);
-
     /**
      * Update a tap exclude region identified by provided id in the window. Touches on this region
      * will neither be dispatched to this window nor change the focus to this window. Passing an
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 60ad926..1cb2765 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30695,21 +30695,11 @@
      */
     public void setPointerIcon(PointerIcon pointerIcon) {
         mMousePointerIcon = pointerIcon;
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            final ViewRootImpl viewRootImpl = getViewRootImpl();
-            if (viewRootImpl == null) {
-                return;
-            }
-            viewRootImpl.refreshPointerIcon();
-        } else {
-            if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
-                return;
-            }
-            try {
-                mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
-            } catch (RemoteException e) {
-            }
+        final ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl == null) {
+            return;
         }
+        viewRootImpl.refreshPointerIcon();
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42b40a1..155c053 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -74,7 +74,6 @@
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -96,6 +95,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -122,7 +122,6 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
-import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.window.flags.Flags.activityWindowInfoFlag;
 import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
@@ -7980,46 +7979,20 @@
         if (event.isStylusPointer() && mIsStylusPointerIconEnabled) {
             pointerIcon = mHandwritingInitiator.onResolvePointerIcon(mContext, event);
         }
-
         if (pointerIcon == null) {
             pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
         }
-
-        if (enablePointerChoreographer()) {
-            if (pointerIcon == null) {
-                pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
-            }
-            if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
-                return true;
-            }
-            mResolvedPointerIcon = pointerIcon;
-
-            InputManagerGlobal.getInstance()
-                    .setPointerIcon(pointerIcon, event.getDisplayId(),
-                            event.getDeviceId(), event.getPointerId(0), getInputToken());
+        if (pointerIcon == null) {
+            pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+        }
+        if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
             return true;
         }
+        mResolvedPointerIcon = pointerIcon;
 
-        final int pointerType = (pointerIcon != null) ?
-                pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
-
-        if (mPointerIconType == null || mPointerIconType != pointerType) {
-            mPointerIconType = pointerType;
-            mCustomPointerIcon = null;
-            if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
-                InputManagerGlobal
-                        .getInstance()
-                        .setPointerIconType(pointerType);
-                return true;
-            }
-        }
-        if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
-                !pointerIcon.equals(mCustomPointerIcon)) {
-            mCustomPointerIcon = pointerIcon;
-            InputManagerGlobal
-                    .getInstance()
-                    .setCustomPointerIcon(mCustomPointerIcon);
-        }
+        InputManagerGlobal.getInstance()
+                .setPointerIcon(pointerIcon, event.getDisplayId(),
+                        event.getDeviceId(), event.getPointerId(0), getInputToken());
         return true;
     }
 
@@ -10623,16 +10596,6 @@
         mHandler.sendMessage(msg);
     }
 
-    public void updatePointerIcon(float x, float y) {
-        final int what = MSG_UPDATE_POINTER_ICON;
-        mHandler.removeMessages(what);
-        final long now = SystemClock.uptimeMillis();
-        final MotionEvent event = MotionEvent.obtain(
-                0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
-        Message msg = mHandler.obtainMessage(what, event);
-        mHandler.sendMessage(msg);
-    }
-
     public void dispatchCheckFocus() {
         if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
             // This will result in a call to checkFocus() below.
@@ -11496,14 +11459,6 @@
         }
 
         @Override
-        public void updatePointerIcon(float x, float y) {
-            final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.updatePointerIcon(x, y);
-            }
-        }
-
-        @Override
         public void dispatchWindowShown() {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index e6367ff..d7d764b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -603,10 +603,6 @@
     }
 
     @Override
-    public void updatePointerIcon(android.view.IWindow window) {
-    }
-
-    @Override
     public void updateTapExcludeRegion(android.view.IWindow window,
             android.graphics.Region region) {
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a073873..cf128fb 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2530,18 +2530,6 @@
                 view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
     }
 
-    private void startStylusHandwritingInternalAsync(
-            @NonNull View view, @Nullable String delegatorPackageName,
-            @HandwritingDelegateFlags int handwritingDelegateFlags,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        Objects.requireNonNull(view);
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-
-        startStylusHandwritingInternal(
-                view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
-    }
-
     private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         if (executor == null || callback == null) {
@@ -2891,7 +2879,7 @@
         if (Flags.homeScreenHandwritingDelegator()) {
             flags = delegateView.getHandwritingDelegateFlags();
         }
-        startStylusHandwritingInternalAsync(
+        acceptStylusHandwritingDelegation(
                 delegateView, delegatorPackageName, flags, executor, callback);
     }
 
@@ -2926,6 +2914,9 @@
             @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         Objects.requireNonNull(delegatorPackageName);
+        Objects.requireNonNull(delegateView);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
 
         startStylusHandwritingInternal(
                 delegateView, delegatorPackageName, flags, executor, callback);
diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java
new file mode 100644
index 0000000..0104d1f
--- /dev/null
+++ b/core/java/com/android/internal/util/NewlineNormalizer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.util;
+
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility class that replaces consecutive empty lines with single new line.
+ * @hide
+ */
+public class NewlineNormalizer {
+
+    private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
+
+    // Private constructor to prevent instantiation
+    private NewlineNormalizer() {}
+
+    /**
+     * Replaces consecutive newlines with a single newline in the input text.
+     */
+    public static String normalizeNewlines(String text) {
+        return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
+    }
+}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c0616d0..3fc4fff 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -18,7 +18,6 @@
 
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.hardware.input.InputManagerGlobal;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -29,7 +28,6 @@
 import android.view.IWindowSession;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.PointerIcon;
 import android.view.ScrollCaptureResponse;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.ImeTracker;
@@ -128,12 +126,6 @@
     }
 
     @Override
-    public void updatePointerIcon(float x, float y) {
-        InputManagerGlobal.getInstance()
-                .setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
-    }
-
-    @Override
     public void dispatchWallpaperCommand(String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         if (sync) {
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index 7f630cb..5d7b33e 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -59,7 +59,8 @@
     static const jclass intClass = FindClassOrDie(env, "java/lang/Integer");
     static const jmethodID byteValue = env->GetMethodID(intClass, "byteValue", "()B");
 
-    viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId);
+    viewport->displayId = ui::LogicalDisplayId{
+            env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId)};
     viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive);
     jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation);
     viewport->orientation = static_cast<ui::Rotation>(orientation);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index bed7768..69f6334 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -165,8 +165,8 @@
     mInfo.ownerUid = gui::Uid{
             static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
     mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
-    mInfo.displayId = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.displayId);
+    mInfo.displayId =
+            ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
 
     jobject inputApplicationHandleObj = env->GetObjectField(obj,
             gInputWindowHandleClassInfo.inputApplicationHandle);
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index ca8752f..06e0d2d 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -135,8 +135,8 @@
     jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
 
     KeyEvent event;
-    event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
-                     metaState, repeatCount, downTime, eventTime);
+    event.initialize(id, deviceId, source, ui::LogicalDisplayId{displayId}, *hmac, action, flags,
+                     keyCode, scanCode, metaState, repeatCount, downTime, eventTime);
     return event;
 }
 
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 3e3af40..f914bee 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,7 +22,6 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
@@ -367,8 +366,8 @@
     ui::Transform transform;
     transform.set(xOffset, yOffset);
     ui::Transform identityTransform;
-    event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0,
-                      flags, edgeFlags, metaState, buttonState,
+    event->initialize(InputEvent::nextId(), deviceId, source, ui::LogicalDisplayId{displayId},
+                      INVALID_HMAC, action, 0, flags, edgeFlags, metaState, buttonState,
                       static_cast<MotionClassification>(classification), transform, xPrecision,
                       yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTimeNanos,
@@ -646,13 +645,13 @@
 
 static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getDisplayId();
+    return static_cast<jint>(event->getDisplayId().val());
 }
 
 static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
                                                         jint displayId) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->setDisplayId(displayId);
+    event->setDisplayId(ui::LogicalDisplayId{displayId});
 }
 
 static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index bc69d1e6..c39d5e2 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -60,7 +60,7 @@
     }
     ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues));
     return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor,
-                          displayInfo.displayId, displayInfo.logicalWidth,
+                          displayInfo.displayId.val(), displayInfo.logicalWidth,
                           displayInfo.logicalHeight, matrixObj.get());
 }
 
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 0433855..bf2fdda 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -329,7 +329,8 @@
 
         InputDeviceInfo info = InputDeviceInfo();
         info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
-                        "keyboard " + std::to_string(keyboardId), true, false, 0);
+                        "keyboard " + std::to_string(keyboardId), true, false,
+                        ui::ADISPLAY_ID_DEFAULT);
         info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
         info.setKeyCharacterMap(*charMap);
 
diff --git a/core/res/res/drawable-car/car_activity_resolver_list_background.xml b/core/res/res/drawable-car/car_activity_resolver_list_background.xml
new file mode 100644
index 0000000..dbbadd8
--- /dev/null
+++ b/core/res/res/drawable-car/car_activity_resolver_list_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="?attr/colorBackgroundFloating" />
+    <corners android:radius="@dimen/car_activity_resolver_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list.xml b/core/res/res/layout-car/car_resolver_list.xml
index 755cbfe..75266b2 100644
--- a/core/res/res/layout-car/car_resolver_list.xml
+++ b/core/res/res/layout-car/car_resolver_list.xml
@@ -18,147 +18,129 @@
 -->
 <com.android.internal.widget.ResolverDrawerLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="@dimen/car_activity_resolver_width"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
     android:id="@id/contentPanel">
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:weightSum="5"
-        android:layout_alwaysShow="true"
-        android:orientation="vertical"
-        android:background="?attr/colorBackgroundFloating"
-        android:elevation="8dp">
+        android:layout_height="wrap_content"
+        android:weightSum="2"
+        android:orientation="vertical" >
+
+        <View android:id="@+id/empty_top"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/transparent" />
 
         <LinearLayout
-            android:id="@+id/button_bar"
-            android:visibility="gone"
-            style="?attr/buttonBarStyle"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_ignoreOffset="true"
-            android:layout_alwaysShow="true"
-            android:layout_hasNestedScrollIndicator="true"
-            android:background="?attr/colorBackgroundFloating"
-            android:orientation="horizontal"
-            android:paddingTop="8dp"
-            android:paddingStart="12dp"
-            android:weightSum="4"
-            android:paddingEnd="12dp"
-            android:elevation="8dp">
+            android:orientation="vertical"
+            android:background="@drawable/car_activity_resolver_list_background">
 
-            <TextView
-                android:id="@+id/profile_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="8dp"
-                android:paddingStart="8dp"
-                android:paddingEnd="8dp"
-                android:textSize="40sp"
-                android:layout_weight="4"
-                android:layout_gravity="left"
-                android:visibility="gone"
-                android:textColor="?attr/colorAccent"
-                android:singleLine="true"/>
-
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="left"
-                android:layout_weight="3"
-                android:paddingTop="8dp"
-                android:layout_below="@id/profile_button"
-                android:textAppearance="?android:attr/textAppearanceLarge"
-                android:paddingBottom="8dp"/>
-
-            <Button
-                android:id="@+id/button_once"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:enabled="false"
-                android:layout_gravity="right"
-                style="?attr/buttonBarButtonStyle"
-                android:text="@string/activity_resolver_use_once"
-                android:layout_weight="0.5"
-                android:onClick="onButtonClick"/>
-
-            <Button
-                android:id="@+id/button_always"
-                android:layout_marginLeft="2dp"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:enabled="false"
-                android:layout_gravity="right"
-                style="?attr/buttonBarButtonStyle"
-                android:text="@string/activity_resolver_use_always"
-                android:layout_weight="0.5"
-                android:onClick="onButtonClick"/>
-        </LinearLayout>
-
-        <FrameLayout
-            android:id="@+id/stub"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="?attr/colorBackgroundFloating"/>
-
-        <TabHost
-            android:id="@+id/profile_tabhost"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:background="?attr/colorBackgroundFloating">
             <LinearLayout
-                android:orientation="vertical"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TabWidget
-                    android:id="@android:id/tabs"
-                    android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/car_activity_resolver_list_background"
+                android:orientation="horizontal"
+                android:paddingVertical="@dimen/car_padding_4"
+                android:paddingHorizontal="@dimen/car_padding_4" >
+                <TextView
+                    android:id="@+id/profile_button"
+                    android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:visibility="gone">
-                </TabWidget>
-                <View
-                    android:id="@+id/resolver_tab_divider"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="1dp"
-                    android:background="?attr/colorBackgroundFloating"
-                    android:foreground="?attr/dividerVertical"
-                    android:layout_marginBottom="8dp"/>
-                <FrameLayout
-                    android:id="@android:id/tabcontent"
+                    android:visibility="gone" />
+
+                <TextView
+                    android:id="@+id/title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="start"
+                    android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
+            </LinearLayout>
+
+            <FrameLayout
+                android:id="@+id/stub"
+                android:visibility="gone"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"/>
+
+            <TabHost
+                android:id="@+id/profile_tabhost"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentTop="true"
+                android:layout_centerHorizontal="true"
+                android:background="?android:attr/colorBackgroundFloating">
+                <LinearLayout
+                    android:orientation="vertical"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
-                    <com.android.internal.app.ResolverViewPager
-                        android:id="@+id/profile_pager"
+                    <TabWidget
+                        android:id="@android:id/tabs"
+                        android:visibility="gone"
                         android:layout_width="match_parent"
-                        android:layout_height="wrap_content"/>
-                </FrameLayout>
+                        android:layout_height="wrap_content">
+                    </TabWidget>
+                    <View
+                        android:id="@+id/resolver_tab_divider"
+                        android:visibility="gone"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content" />
+                    <FrameLayout
+                        android:id="@android:id/tabcontent"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content">
+                        <com.android.internal.app.ResolverViewPager
+                            android:id="@+id/profile_pager"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"/>
+                    </FrameLayout>
+                </LinearLayout>
+            </TabHost>
+
+            <LinearLayout
+                android:id="@+id/button_bar"
+                android:visibility="gone"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="@dimen/car_padding_4"
+                android:layout_marginHorizontal="@dimen/car_padding_4"
+                android:padding="0dp"
+                android:gravity="center"
+                android:background="@drawable/car_activity_resolver_list_background"
+                android:orientation="vertical">
+
+                <Button
+                    android:id="@+id/button_once"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/car_button_height"
+                    android:enabled="false"
+                    android:layout_gravity="center"
+                    android:layout_marginBottom="@dimen/car_padding_2"
+                    android:text="@string/activity_resolver_use_once"
+                    android:onClick="onButtonClick"/>
+
+                <Button
+                    android:id="@+id/button_always"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/car_button_height"
+                    android:enabled="false"
+                    android:layout_gravity="center"
+                    android:text="@string/activity_resolver_use_always"
+                    android:onClick="onButtonClick"/>
             </LinearLayout>
-        </TabHost>
+        </LinearLayout>
 
-        <View
-            android:layout_alwaysShow="true"
+        <View android:id="@+id/empty_bottom"
             android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="?attr/colorBackgroundFloating"
-            android:foreground="?attr/dividerVertical"/>
-
-        <TextView android:id="@+id/empty"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:background="?attr/colorBackgroundFloating"
-                  android:elevation="8dp"
-                  android:layout_alwaysShow="true"
-                  android:text="@string/noApplications"
-                  android:padding="32dp"
-                  android:gravity="center"
-                  android:visibility="gone"/>
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/transparent" />
 
     </LinearLayout>
 
-</com.android.internal.widget.ResolverDrawerLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list_with_default.xml b/core/res/res/layout-car/car_resolver_list_with_default.xml
index 5e450b2..0beb95b 100644
--- a/core/res/res/layout-car/car_resolver_list_with_default.xml
+++ b/core/res/res/layout-car/car_resolver_list_with_default.xml
@@ -18,179 +18,162 @@
 -->
 <com.android.internal.widget.ResolverDrawerLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:maxCollapsedHeight="200dp"
+    android:layout_width="@dimen/car_activity_resolver_width"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
     android:id="@id/contentPanel">
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:weightSum="5"
-        android:layout_alwaysShow="true"
-        android:orientation="vertical"
-        android:background="?attr/colorBackgroundFloating"
-        android:elevation="8dp">
+        android:layout_height="wrap_content"
+        android:weightSum="2"
+        android:orientation="vertical" >
+
+        <View android:id="@+id/empty_top"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/transparent" />
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_weight="0.5"
-            android:orientation="horizontal">
+            android:orientation="vertical"
+            android:layout_gravity="center"
+            android:background="@drawable/car_activity_resolver_list_background">
 
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="60dp"
-                android:layout_height="60dp"
-                android:layout_gravity="start|top"
-                android:layout_marginStart="10dp"
-                android:layout_marginEnd="5dp"
-                android:layout_marginTop="10dp"
-                android:src="@drawable/resolver_icon_placeholder"
-                android:scaleType="fitCenter"/>
+            <FrameLayout
+                android:id="@+id/stub"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/car_activity_resolver_list_background"/>
 
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="0dp"
-                android:layout_weight="1"
-                android:layout_height="?attr/listPreferredItemHeight"
-                android:layout_marginStart="16dp"
-                android:textAppearance="?android:attr/textAppearanceLarge"
-                android:gravity="start|center_vertical"
-                android:paddingEnd="16dp"/>
 
             <LinearLayout
-                android:id="@+id/profile_button"
-                android:layout_width="wrap_content"
-                android:layout_height="48dp"
-                android:layout_marginTop="4dp"
-                android:layout_marginEnd="4dp"
-                android:paddingStart="8dp"
-                android:paddingEnd="8dp"
-                android:paddingTop="4dp"
-                android:paddingBottom="4dp"
-                android:focusable="true"
-                android:visibility="gone"
-                style="?attr/borderlessButtonStyle">
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/car_activity_resolver_list_item_height"
+                android:orientation="horizontal">
+
+                <RadioButton
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:focusable="false"
+                    android:clickable="false"
+                    android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+                    android:layout_gravity="start|center_vertical"
+                    android:checked="true"/>
 
                 <ImageView
                     android:id="@+id/icon"
-                    android:layout_width="24dp"
-                    android:layout_height="24dp"
+                    android:layout_width="@dimen/car_icon_size"
+                    android:layout_height="@dimen/car_icon_size"
                     android:layout_gravity="start|center_vertical"
-                    android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
-                    android:layout_marginTop="12dp"
-                    android:layout_marginBottom="12dp"
+                    android:layout_marginStart="@dimen/car_padding_4"
+                    android:src="@drawable/resolver_icon_placeholder"
                     android:scaleType="fitCenter"/>
 
                 <TextView
-                    android:id="@id/text1"
+                    android:id="@+id/title"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_gravity="start|center_vertical"
-                    android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
-                    android:textAppearance="?attr/textAppearanceButton"
-                    android:textColor="?attr/textColorPrimary"
-                    android:minLines="1"
-                    android:maxLines="1"
-                    android:ellipsize="marquee"/>
-            </LinearLayout>
-        </LinearLayout>
+                    android:layout_marginHorizontal="?attr/listPreferredItemPaddingStart"
+                    style="?android:attr/textAppearanceListItem"
+                    android:layout_gravity="start|center_vertical" />
 
-        <LinearLayout
-            android:id="@+id/button_bar"
-            android:visibility="gone"
-            style="?attr/buttonBarStyle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alwaysShow="true"
-            android:gravity="end|center_vertical"
-            android:layout_weight="0.5"
-            android:orientation="horizontal"
-            android:layoutDirection="locale"
-            android:measureWithLargestChild="true"
-            android:paddingTop="8dp"
-            android:paddingBottom="8dp"
-            android:paddingStart="12dp"
-            android:paddingEnd="12dp"
-            android:elevation="8dp">
-
-            <Button
-                android:id="@+id/button_once"
-                android:layout_width="wrap_content"
-                android:layout_gravity="start"
-                android:maxLines="2"
-                style="?attr/buttonBarButtonStyle"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content"
-                android:enabled="false"
-                android:text="@string/activity_resolver_use_once"
-                android:onClick="onButtonClick"/>
-
-            <Button
-                android:id="@+id/button_always"
-                android:layout_width="wrap_content"
-                android:layout_gravity="end"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_height="wrap_content"
-                android:enabled="false"
-                android:text="@string/activity_resolver_use_always"
-                android:onClick="onButtonClick"/>
-        </LinearLayout>
-
-        <FrameLayout
-            android:id="@+id/stub"
-            android:layout_alwaysShow="true"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="?attr/colorBackgroundFloating"/>
-
-        <TabHost
-            android:layout_alwaysShow="true"
-            android:id="@+id/profile_tabhost"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:background="?attr/colorBackgroundFloating">
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TabWidget
-                    android:id="@android:id/tabs"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:visibility="gone">
-                </TabWidget>
-                <View
-                    android:id="@+id/resolver_tab_divider"
+                <LinearLayout
+                    android:id="@+id/profile_button"
                     android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="1dp"
-                    android:background="?attr/colorBackgroundFloating"
-                    android:foreground="?attr/dividerVertical"
-                    android:layout_marginBottom="8dp"/>
-                <FrameLayout
-                    android:id="@android:id/tabcontent"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <ImageView
+                        android:id="@+id/icon"
+                        android:visibility="gone"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+
+                    <TextView
+                        android:id="@id/text1"
+                        android:visibility="gone"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <TabHost
+                android:id="@+id/profile_tabhost"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentTop="true"
+                android:layout_centerHorizontal="true"
+                android:background="?attr/colorBackgroundFloating">
+                <LinearLayout
+                    android:orientation="vertical"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
-                    <com.android.internal.app.ResolverViewPager
-                        android:id="@+id/profile_pager"
+                    <TabWidget
+                        android:id="@android:id/tabs"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:visibility="gone">
+                    </TabWidget>
+                    <View
+                        android:id="@+id/resolver_tab_divider"
+                        android:visibility="gone"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content" />
+                    <FrameLayout
+                        android:id="@android:id/tabcontent"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content">
-                    </com.android.internal.app.ResolverViewPager>
-                </FrameLayout>
-            </LinearLayout>
-        </TabHost>
+                        <com.android.internal.app.ResolverViewPager
+                            android:id="@+id/profile_pager"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+                        </com.android.internal.app.ResolverViewPager>
+                    </FrameLayout>
+                </LinearLayout>
+            </TabHost>
 
-        <View
+            <LinearLayout
+                android:id="@+id/button_bar"
+                android:visibility="gone"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="@dimen/car_padding_4"
+                android:layout_marginHorizontal="@dimen/car_padding_4"
+                android:gravity="center"
+                android:background="@drawable/car_activity_resolver_list_background"
+                android:orientation="vertical">
+
+                <Button
+                    android:id="@+id/button_once"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/car_button_height"
+                    android:enabled="false"
+                    android:layout_gravity="center"
+                    android:layout_marginBottom="@dimen/car_padding_2"
+                    android:text="@string/activity_resolver_use_once"
+                    android:onClick="onButtonClick"/>
+
+                <Button
+                    android:id="@+id/button_always"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/car_button_height"
+                    android:enabled="false"
+                    android:layout_gravity="center"
+                    android:text="@string/activity_resolver_use_always"
+                    android:onClick="onButtonClick"/>
+            </LinearLayout>
+        </LinearLayout>
+
+        <View android:id="@+id/empty_bottom"
             android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="?attr/dividerVertical"/>
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/transparent" />
+
     </LinearLayout>
 
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index c5dddb8..99377ff 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -151,4 +151,10 @@
     <dimen name="action_bar_button_margin">@*android:dimen/car_padding_4</dimen>
     <dimen name="action_bar_button_max_width">268dp</dimen>
     <dimen name="action_bar_toggle_internal_padding">@*android:dimen/car_padding_3</dimen>
+
+    <!-- Intent Resolver -->
+    <dimen name="car_activity_resolver_width">706dp</dimen>
+    <dimen name="car_activity_resolver_list_item_height">96dp</dimen>
+    <dimen name="car_activity_resolver_list_max_height">256dp</dimen>
+    <dimen name="car_activity_resolver_corner_radius">24dp</dimen>
 </resources>
diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
new file mode 100644
index 0000000..bcdac61
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util;
+
+import static junit.framework.Assert.assertEquals;
+
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link NewlineNormalizer}
+ * @hide
+ */
+@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
+@RunWith(AndroidJUnit4.class)
+public class NewlineNormalizerTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testEmptyInput() {
+        assertEquals("", NewlineNormalizer.normalizeNewlines(""));
+    }
+
+    @Test
+    public void testSingleNewline() {
+        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
+    }
+
+    @Test
+    public void testMultipleConsecutiveNewlines() {
+        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
+    }
+
+    @Test
+    public void testNewlinesWithSpacesAndTabs() {
+        String input = "Line 1\n  \n \t \n\tLine 2";
+        // Adjusted expected output to include the tab character
+        String expected = "Line 1\n\tLine 2";
+        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+    }
+
+    @Test
+    public void testMixedNewlineCharacters() {
+        String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
+        String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
+        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+    }
+}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
deleted file mode 100644
index d1d7c14..0000000
--- a/keystore/java/android/security/KeyStore.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-/**
- * This class provides some constants and helper methods related to Android's Keystore service.
- * This class was originally much larger, but its functionality was superseded by other classes.
- * It now just contains a few remaining pieces for which the users haven't been updated yet.
- * You may be looking for {@link java.security.KeyStore} instead.
- *
- * @hide
- */
-public class KeyStore {
-
-    // Used for UID field to indicate the calling UID.
-    public static final int UID_SELF = -1;
-}
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
index fe47503..0ac6265 100644
--- a/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -5,8 +5,6 @@
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
 -packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
 -packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 3c788b1..7bceb2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2341,8 +2341,8 @@
 
         showScrim(true, null /* runnable */);
         updateBubbleShadows(mIsExpanded);
-        updateBadges(false /* setBadgeForCollapsedStack */);
         mBubbleContainer.setActiveController(mExpandedAnimationController);
+        updateBadges(false /* setBadgeForCollapsedStack */);
         updateOverflowVisibility();
         updatePointerPosition(false /* forIme */);
         mExpandedAnimationController.expandFromStack(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 5e063a2..da414cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -389,9 +389,6 @@
         public void dispatchDragEvent(DragEvent event) {}
 
         @Override
-        public void updatePointerIcon(float x, float y) {}
-
-        @Override
         public void dispatchWindowShown() {}
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index f195f95..3ab1fad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -81,6 +81,10 @@
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+        if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+            // Don't show the SCM button for freeform tasks
+            mHasSizeCompat &= !taskInfo.isFreeform();
+        }
         mCameraCompatControlState =
                 taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
         mCompatUIHintsState = compatUIHintsState;
@@ -136,6 +140,10 @@
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+        if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+            // Don't show the SCM button for freeform tasks
+            mHasSizeCompat &= !taskInfo.isFreeform();
+        }
         mCameraCompatControlState =
                 taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 9bf9fa7..b41454d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -65,7 +65,7 @@
             "persist.wm.debug.desktop_use_window_shadows_focused_window", false);
 
     /**
-     * Flag to indicate whether to apply shadows to windows in desktop mode.
+     * Flag to indicate whether to use rounded corners for windows in desktop mode.
      */
     private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_use_rounded_corners", true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
new file mode 100644
index 0000000..6da3741
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("DesktopModeUtils")
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.ActivityInfo.isFixedOrientationLandscape
+import android.content.pm.ActivityInfo.isFixedOrientationPortrait
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.graphics.Rect
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.common.DisplayLayout
+
+
+val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = SystemProperties
+        .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+
+val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties
+        .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
+
+
+/**
+ * Calculates the initial bounds required for an application to fill a scale of the display bounds
+ * without any letterboxing. This is done by taking into account the applications fullscreen size,
+ * aspect ratio, orientation and resizability to calculate an area this is compatible with the
+ * applications previous configuration.
+ */
+fun calculateInitialBounds(
+    displayLayout: DisplayLayout,
+    taskInfo: RunningTaskInfo,
+    scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE
+): Rect {
+    val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
+    val appAspectRatio = calculateAspectRatio(taskInfo)
+    val idealSize = calculateIdealSize(screenBounds, scale)
+    // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated.
+    // Instead default to the desired initial bounds.
+    val topActivityInfo = taskInfo.topActivityInfo
+        ?: return positionInScreen(idealSize, screenBounds)
+
+    val initialSize: Size = when (taskInfo.configuration.orientation) {
+        ORIENTATION_LANDSCAPE -> {
+            if (taskInfo.isResizeable) {
+                if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
+                    // Respect apps fullscreen width
+                    Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+                } else {
+                    idealSize
+                }
+            } else {
+                maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+                    appAspectRatio)
+            }
+        }
+        ORIENTATION_PORTRAIT -> {
+            val customPortraitWidthForLandscapeApp = screenBounds.width() -
+                    (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
+            if (taskInfo.isResizeable) {
+                if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+                    // Respect apps fullscreen height and apply custom app width
+                    Size(customPortraitWidthForLandscapeApp,
+                        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight)
+                } else {
+                    idealSize
+                }
+            } else {
+                if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+                    // Apply custom app width and calculate maximum size
+                    maximumSizeMaintainingAspectRatio(
+                        taskInfo,
+                        Size(customPortraitWidthForLandscapeApp, idealSize.height),
+                        appAspectRatio)
+                } else {
+                    maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+                        appAspectRatio)
+                }
+            }
+        }
+        else -> {
+            idealSize
+        }
+    }
+
+    return positionInScreen(initialSize, screenBounds)
+}
+
+/**
+ * Calculates the largest size that can fit in a given area while maintaining a specific aspect
+ * ratio.
+ */
+private fun maximumSizeMaintainingAspectRatio(
+    taskInfo: RunningTaskInfo,
+    targetArea: Size,
+    aspectRatio: Float
+): Size {
+    val targetHeight = targetArea.height
+    val targetWidth = targetArea.width
+    val finalHeight: Int
+    val finalWidth: Int
+    if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+        val tempWidth = (targetHeight / aspectRatio).toInt()
+        if (tempWidth <= targetWidth) {
+            finalHeight = targetHeight
+            finalWidth = tempWidth
+        } else {
+            finalWidth = targetWidth
+            finalHeight = (finalWidth * aspectRatio).toInt()
+        }
+    } else {
+        val tempWidth = (targetHeight * aspectRatio).toInt()
+        if (tempWidth <= targetWidth) {
+            finalHeight = targetHeight
+            finalWidth = tempWidth
+        } else {
+            finalWidth = targetWidth
+            finalHeight = (finalWidth / aspectRatio).toInt()
+        }
+    }
+    return Size(finalWidth, finalHeight)
+}
+
+/**
+ * Calculates the aspect ratio of an activity from its fullscreen bounds.
+ */
+private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+    if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+        val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
+        val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+        return maxOf(appLetterboxWidth, appLetterboxHeight) /
+                minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
+    }
+    val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
+    return maxOf(appBounds.height(), appBounds.width()) /
+                minOf(appBounds.height(), appBounds.width()).toFloat()
+}
+
+/**
+ * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
+ * scale of the screen bounds.
+ */
+private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size {
+    val width = (screenBounds.width() * scale).toInt()
+    val height = (screenBounds.height() * scale).toInt()
+    return Size(width, height)
+}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the screen.
+ */
+private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
+    // TODO(b/325240051): Position apps with bottom heavy offset
+    val heightOffset = (screenBounds.height() - desiredSize.height) / 2
+    val widthOffset = (screenBounds.width() - desiredSize.width) / 2
+    return Rect(widthOffset, heightOffset,
+        desiredSize.width + widthOffset, desiredSize.height + heightOffset)
+}
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 f7bfb86..b0d5923 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
@@ -47,6 +47,7 @@
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -85,7 +86,6 @@
 import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
-import com.android.wm.shell.windowdecor.extension.isFreeform
 import com.android.wm.shell.windowdecor.extension.isFullscreen
 import java.io.PrintWriter
 import java.util.Optional
@@ -203,6 +203,11 @@
         dragAndDropController.addListener(this)
     }
 
+    @VisibleForTesting
+    fun getVisualIndicator(): DesktopModeVisualIndicator? {
+        return visualIndicator
+    }
+
     fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
         toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
         enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -605,8 +610,9 @@
     }
 
     /**
-     * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds
-     * if available or the default bounds otherwise.
+     * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the
+     * stable bounds) and a free floating state (either the last saved bounds if available or the
+     * default bounds otherwise).
      */
     fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -623,7 +629,11 @@
             if (taskBoundsBeforeMaximize != null) {
                 destinationBounds.set(taskBoundsBeforeMaximize)
             } else {
-                destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+                if (Flags.enableWindowingDynamicInitialBounds()){
+                    destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
+                } else {
+                    destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+                }
             }
         } else {
             // Save current bounds so that task can be restored back to original bounds if necessary
@@ -1011,6 +1021,7 @@
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo
     ) {
+        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
         val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
         val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
         val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
@@ -1019,6 +1030,9 @@
         } else {
             WINDOWING_MODE_FREEFORM
         }
+        if (Flags.enableWindowingDynamicInitialBounds()) {
+            wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
+        }
         wct.setWindowingMode(taskInfo.token, targetWindowingMode)
         wct.reorder(taskInfo.token, true /* onTop */)
         if (isDesktopDensityOverrideSet()) {
@@ -1239,13 +1253,17 @@
      * @param y height of drag, to be checked against status bar height.
      */
     fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
-        val indicator = visualIndicator ?: return
+        val indicator = getVisualIndicator() ?: return
         val indicatorType = indicator
             .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
         when (indicatorType) {
             DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
                 val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-                finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+                if (Flags.enableWindowingDynamicInitialBounds()) {
+                    finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
+                } else {
+                    finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+                }
             }
             DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
             DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index ad29d15..19af3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -52,7 +52,7 @@
     WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
-            Consts.TAG_WM_SHELL),
+            Consts.TAG_WM_DESKTOP_MODE),
     WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -120,6 +120,7 @@
         private static final String TAG_WM_SHELL = "WindowManagerShell";
         private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
         private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
+        private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
 
         private static final boolean ENABLE_DEBUG = true;
         private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4d02ec2..968b27b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -426,7 +426,8 @@
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "Converting mixed transition into a keyguard transition");
                     // Consume the original mixed transition
-                    onTransitionConsumed(transition, false, null);
+                    mActiveTransitions.remove(mixed);
+                    mixed.onTransitionConsumed(transition, false, null);
                     return true;
                 } else {
                     // Keyguard handler cannot handle it, process through original mixed
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 f3ef7c1..dfdb58a 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
@@ -278,9 +278,11 @@
             public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
                 if (visible && stage != STAGE_TYPE_UNDEFINED) {
                     DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
-                    if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
-                        mDesktopTasksController.moveToSplit(decor.mTaskInfo);
+                    if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext)
+                            || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                        return;
                     }
+                    mDesktopTasksController.moveToSplit(decor.mTaskInfo);
                 }
             }
         });
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 f7516da..4c347ad 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
@@ -110,6 +110,8 @@
 
     private ResizeVeil mResizeVeil;
     private Bitmap mAppIconBitmap;
+    private Bitmap mResizeVeilBitmap;
+
     private CharSequence mAppName;
 
     private ExclusionRegionListener mExclusionRegionListener;
@@ -468,11 +470,15 @@
             PackageManager pm = mContext.getApplicationContext().getPackageManager();
             final IconProvider provider = new IconProvider(mContext);
             final Drawable appIconDrawable = provider.getIcon(activityInfo);
-            final Resources resources = mContext.getResources();
-            final BaseIconFactory factory = new BaseIconFactory(mContext,
-                    resources.getDisplayMetrics().densityDpi,
-                    resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
-            mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+            final BaseIconFactory headerIconFactory = createIconFactory(mContext,
+                    R.dimen.desktop_mode_caption_icon_radius);
+            mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
+            final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
+                    R.dimen.desktop_mode_resize_veil_icon_size);
+            mResizeVeilBitmap = resizeVeilIconFactory
+                    .createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
             final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
             mAppName = pm.getApplicationLabel(applicationInfo);
         } finally {
@@ -480,6 +486,13 @@
         }
     }
 
+    private BaseIconFactory createIconFactory(Context context, int dimensions) {
+        final Resources resources = context.getResources();
+        final int densityDpi = resources.getDisplayMetrics().densityDpi;
+        final int iconSize = resources.getDimensionPixelSize(dimensions);
+        return new BaseIconFactory(context, densityDpi, iconSize);
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
@@ -495,7 +508,7 @@
     private void createResizeVeilIfNeeded() {
         if (mResizeVeil != null) return;
         loadAppInfoIfNeeded();
-        mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo,
+        mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo,
                 mTaskSurface, mSurfaceControlTransactionSupplier);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 9624d46..5379ca6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -24,7 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
-import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -54,6 +54,7 @@
 import android.view.WindowManagerGlobal;
 import android.window.InputTransferToken;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -399,12 +400,17 @@
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
                         int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
+                        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                                "%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
                         mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
                                 rawX, rawY);
                         // Increase the input sink region to cover the whole screen; this is to
                         // prevent input and focus from going to other tasks during a drag resize.
                         updateInputSinkRegionForDrag(mDragStartTaskBounds);
                         result = true;
+                    } else {
+                        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                                "%s: Handling action down, but ignore event", TAG);
                     }
                     break;
                 }
@@ -499,12 +505,10 @@
             // where views in the task can receive input events because we can't set touch regions
             // of input sinks to have rounded corners.
             if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
-                if (enablePointerChoreographer()) {
-                    mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
-                            displayId, deviceId, pointerId, mInputChannel.getToken());
-                } else {
-                    mInputManager.setPointerIconType(cursorType);
-                }
+                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: update pointer icon from %d to %d",
+                        TAG, mLastCursorType, cursorType);
+                mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
+                        displayId, deviceId, pointerId, mInputChannel.getToken());
                 mLastCursorType = cursorType;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index eafb569..4f513f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,6 +33,9 @@
 import android.util.Size;
 import android.view.MotionEvent;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.wm.shell.R;
 
 import java.util.Objects;
@@ -41,6 +44,11 @@
  * Geometry for a drag resize region for a particular window.
  */
 final class DragResizeWindowGeometry {
+    // TODO(b/337264971) clean up when no longer needed
+    @VisibleForTesting static final boolean DEBUG = true;
+    // The additional width to apply to edge resize bounds just for logging when a touch is
+    // close.
+    @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
     private final int mTaskCornerRadius;
     private final Size mTaskSize;
     // The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -51,10 +59,9 @@
     // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
     private final @NonNull TaskCorners mFineTaskCorners;
     // The bounds for each edge drag region, which can resize the task in one direction.
-    private final @NonNull Rect mTopEdgeBounds;
-    private final @NonNull Rect mLeftEdgeBounds;
-    private final @NonNull Rect mRightEdgeBounds;
-    private final @NonNull Rect mBottomEdgeBounds;
+    private final @NonNull TaskEdges mTaskEdges;
+    // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
+    private final @Nullable TaskEdges mDebugTaskEdges;
 
     DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
             int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -66,26 +73,12 @@
         mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
 
         // Save touch areas for each edge.
-        mTopEdgeBounds = new Rect(
-                -mResizeHandleThickness,
-                -mResizeHandleThickness,
-                mTaskSize.getWidth() + mResizeHandleThickness,
-                0);
-        mLeftEdgeBounds = new Rect(
-                -mResizeHandleThickness,
-                0,
-                0,
-                mTaskSize.getHeight());
-        mRightEdgeBounds = new Rect(
-                mTaskSize.getWidth(),
-                0,
-                mTaskSize.getWidth() + mResizeHandleThickness,
-                mTaskSize.getHeight());
-        mBottomEdgeBounds = new Rect(
-                -mResizeHandleThickness,
-                mTaskSize.getHeight(),
-                mTaskSize.getWidth() + mResizeHandleThickness,
-                mTaskSize.getHeight() + mResizeHandleThickness);
+        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
+        if (DEBUG) {
+            mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
+        } else {
+            mDebugTaskEdges = null;
+        }
     }
 
     /**
@@ -127,10 +120,13 @@
      */
     void union(@NonNull Region region) {
         // Apply the edge resize regions.
-        region.union(mTopEdgeBounds);
-        region.union(mLeftEdgeBounds);
-        region.union(mRightEdgeBounds);
-        region.union(mBottomEdgeBounds);
+        if (inDebugMode()) {
+            // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
+            // touch due to the size of the edge region.
+            mDebugTaskEdges.union(region);
+        } else {
+            mTaskEdges.union(region);
+        }
 
         if (enableWindowingEdgeDragResize()) {
             // Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -216,6 +212,10 @@
 
     @DragPositioningCallback.CtrlType
     private int calculateEdgeResizeCtrlType(float x, float y) {
+        if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
+                    && !mTaskEdges.contains((int) x, (int) y))) {
+            return CTRL_TYPE_UNDEFINED;
+        }
         int ctrlType = CTRL_TYPE_UNDEFINED;
         // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
         // sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -306,10 +306,9 @@
                 && this.mResizeHandleThickness == other.mResizeHandleThickness
                 && this.mFineTaskCorners.equals(other.mFineTaskCorners)
                 && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
-                && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
-                && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
-                && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
-                && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+                && (inDebugMode()
+                        ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
+                        : this.mTaskEdges.equals(other.mTaskEdges));
     }
 
     @Override
@@ -320,10 +319,11 @@
                 mResizeHandleThickness,
                 mFineTaskCorners,
                 mLargeTaskCorners,
-                mTopEdgeBounds,
-                mLeftEdgeBounds,
-                mRightEdgeBounds,
-                mBottomEdgeBounds);
+                (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
+    }
+
+    private boolean inDebugMode() {
+        return DEBUG && mDebugTaskEdges != null;
     }
 
     /**
@@ -431,4 +431,92 @@
                     mRightBottomCornerBounds);
         }
     }
+
+    /**
+     * Representation of the drag resize regions at the edges of the window.
+     */
+    private static class TaskEdges {
+        private final @NonNull Rect mTopEdgeBounds;
+        private final @NonNull Rect mLeftEdgeBounds;
+        private final @NonNull Rect mRightEdgeBounds;
+        private final @NonNull Rect mBottomEdgeBounds;
+        private final @NonNull Region mRegion;
+
+        private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) {
+            // Save touch areas for each edge.
+            mTopEdgeBounds = new Rect(
+                    -resizeHandleThickness,
+                    -resizeHandleThickness,
+                    taskSize.getWidth() + resizeHandleThickness,
+                    0);
+            mLeftEdgeBounds = new Rect(
+                    -resizeHandleThickness,
+                    0,
+                    0,
+                    taskSize.getHeight());
+            mRightEdgeBounds = new Rect(
+                    taskSize.getWidth(),
+                    0,
+                    taskSize.getWidth() + resizeHandleThickness,
+                    taskSize.getHeight());
+            mBottomEdgeBounds = new Rect(
+                    -resizeHandleThickness,
+                    taskSize.getHeight(),
+                    taskSize.getWidth() + resizeHandleThickness,
+                    taskSize.getHeight() + resizeHandleThickness);
+
+            mRegion = new Region();
+            mRegion.union(mTopEdgeBounds);
+            mRegion.union(mLeftEdgeBounds);
+            mRegion.union(mRightEdgeBounds);
+            mRegion.union(mBottomEdgeBounds);
+        }
+
+        /**
+         * Returns {@code true} if the edges contain the given point.
+         */
+        private boolean contains(int x, int y) {
+            return mRegion.contains(x, y);
+        }
+
+        /**
+         * Updates the region to include all four corners.
+         */
+        private void union(Region region) {
+            region.union(mTopEdgeBounds);
+            region.union(mLeftEdgeBounds);
+            region.union(mRightEdgeBounds);
+            region.union(mBottomEdgeBounds);
+        }
+
+        @Override
+        public String toString() {
+            return "TaskEdges for the"
+                    + " top " + mTopEdgeBounds
+                    + " left " + mLeftEdgeBounds
+                    + " right " + mRightEdgeBounds
+                    + " bottom " + mBottomEdgeBounds;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) return false;
+            if (this == obj) return true;
+            if (!(obj instanceof TaskEdges other)) return false;
+
+            return this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+                    && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+                    && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+                    && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(
+                    mTopEdgeBounds,
+                    mLeftEdgeBounds,
+                    mRightEdgeBounds,
+                    mBottomEdgeBounds);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index a2293d5..ec20471 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.windowdecor.extension
 
 import android.app.TaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
 import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
@@ -36,6 +35,3 @@
 
 val TaskInfo.isFullscreen: Boolean
     get() = windowingMode == WINDOWING_MODE_FULLSCREEN
-
-val TaskInfo.isFreeform: Boolean
-    get() = windowingMode == WINDOWING_MODE_FREEFORM
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 df8a222..3f76c4f 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
@@ -24,6 +24,12 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
@@ -101,7 +107,9 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.capture
 import org.mockito.quality.Strictness
@@ -141,6 +149,7 @@
     @Mock lateinit var dragAndDropController: DragAndDropController
     @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
     @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
+    @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -154,6 +163,15 @@
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
 
+    private val DISPLAY_DIMENSION_SHORT = 1600
+    private val DISPLAY_DIMENSION_LONG = 2560
+    private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400)
+    private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240)
+    private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880)
+    private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400)
+    private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861)
+    private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400)
+
     @Before
     fun setUp() {
         mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
@@ -161,7 +179,7 @@
         whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
         doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
 
-        shellInit = Mockito.spy(ShellInit(testExecutor))
+        shellInit = spy(ShellInit(testExecutor))
         desktopModeTaskRepository = DesktopModeTaskRepository()
         desktopTasksLimiter =
                 DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
@@ -464,6 +482,135 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask()
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+            shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
     fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
         val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -1225,6 +1372,185 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask()
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+            shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
     fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
         val task = setUpFreeformTask()
         val mockSurface = mock(SurfaceControl::class.java)
@@ -1276,8 +1602,7 @@
         controller.toggleDesktopTaskSize(task)
         // Assert bounds set to stable bounds
         val wct = getLatestToggleResizeDesktopTaskWct()
-        assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
-                .isEqualTo(STABLE_BOUNDS)
+        assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
     }
 
     @Test
@@ -1304,8 +1629,7 @@
 
         // Assert bounds set to last bounds before maximize
         val wct = getLatestToggleResizeDesktopTaskWct()
-        assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
-                .isEqualTo(boundsBeforeMaximize)
+        assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
     }
 
     @Test
@@ -1346,14 +1670,65 @@
         return task
     }
 
-    private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+    private fun setUpFullscreenTask(
+        displayId: Int = DEFAULT_DISPLAY,
+        isResizable: Boolean = true,
+        windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+        deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+        screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+        shouldLetterbox: Boolean = false
+    ): RunningTaskInfo {
         val task = createFullscreenTask(displayId)
+        val activityInfo = ActivityInfo()
+        activityInfo.screenOrientation = screenOrientation
+        with(task) {
+            topActivityInfo = activityInfo
+            isResizeable = isResizable
+            configuration.orientation = deviceOrientation
+            configuration.windowConfiguration.windowingMode = windowingMode
+
+            if (shouldLetterbox) {
+                if (deviceOrientation == ORIENTATION_LANDSCAPE &&
+                    screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
+                    // Letterbox to portrait size
+                    appCompatTaskInfo.topActivityBoundsLetterboxed = true
+                    appCompatTaskInfo.topActivityLetterboxWidth = 1200
+                    appCompatTaskInfo.topActivityLetterboxHeight = 1600
+                } else if (deviceOrientation == ORIENTATION_PORTRAIT &&
+                    screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
+                    // Letterbox to landscape size
+                    appCompatTaskInfo.topActivityBoundsLetterboxed = true
+                    appCompatTaskInfo.topActivityLetterboxWidth = 1600
+                    appCompatTaskInfo.topActivityLetterboxHeight = 1200
+                }
+            } else {
+                appCompatTaskInfo.topActivityBoundsLetterboxed = false
+            }
+
+            if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+                configuration.windowConfiguration.appBounds = Rect(0, 0,
+                    DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+            } else {
+                configuration.windowConfiguration.appBounds = Rect(0, 0,
+                    DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+            }
+        }
         whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
     }
 
+    private fun setUpLandscapeDisplay() {
+        whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
+        whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+    }
+
+    private fun setUpPortraitDisplay() {
+        whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
+        whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+    }
+
     private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createSplitScreenTask(displayId)
         whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
@@ -1418,6 +1793,17 @@
         return arg.value
     }
 
+    private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
+        val arg: ArgumentCaptor<WindowContainerTransaction> =
+            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        if (ENABLE_SHELL_TRANSITIONS) {
+            verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
+        } else {
+            verify(shellTaskOrganizer).applyTransaction(capture(arg))
+        }
+        return arg.value
+    }
+
     private fun getLatestExitDesktopWct(): WindowContainerTransaction {
         val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         if (ENABLE_SHELL_TRANSITIONS) {
@@ -1429,6 +1815,10 @@
         return arg.value
     }
 
+    private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+        wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+
+
     private fun verifyWCTNotExecuted() {
         if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 82e5a1c..5464508 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -56,6 +56,8 @@
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
     private static final int EDGE_RESIZE_THICKNESS = 15;
+    private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
+            + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -90,13 +92,14 @@
                                 EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
                                 EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
-                .addEqualityGroup(
+                .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
-                                LARGE_CORNER_SIZE + 5),
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
+                .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
-                                LARGE_CORNER_SIZE + 5))
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
                 .testEquals();
     }
 
@@ -122,21 +125,21 @@
     private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
         assertThat(region.contains(point.x, point.y)).isTrue();
         // Horizontally along the edge is still contained.
-        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
-        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
         // Vertically along the edge is not contained.
-        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
     }
 
     private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
         assertThat(region.contains(point.x, point.y)).isTrue();
         // Horizontally along the edge is not contained.
-        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
-        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
         // Vertically along the edge is contained.
-        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
     }
 
     /**
@@ -148,7 +151,10 @@
     public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
         Region region = new Region();
         GEOMETRY.union(region);
-        final int cornerRadius = LARGE_CORNER_SIZE / 2;
+        // Make sure we're choosing a point outside of any debug region buffer.
+        final int cornerRadius = DragResizeWindowGeometry.DEBUG
+                ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+                : LARGE_CORNER_SIZE / 2;
 
         new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
     }
@@ -162,7 +168,9 @@
     public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
         Region region = new Region();
         GEOMETRY.union(region);
-        final int cornerRadius = FINE_CORNER_SIZE / 2;
+        final int cornerRadius = DragResizeWindowGeometry.DEBUG
+                ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+                : LARGE_CORNER_SIZE / 2;
 
         new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
     }
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 6a46544..5cf5a1d 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -117,7 +117,7 @@
     return {mLocked.pointerX, mLocked.pointerY};
 }
 
-int32_t MouseCursorController::getDisplayId() const {
+ui::LogicalDisplayId MouseCursorController::getDisplayId() const {
     std::scoped_lock lock(mLock);
     return mLocked.viewport.displayId;
 }
@@ -467,10 +467,10 @@
 
     std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
     /*
-     * Using -1 for displayId here to avoid removing the callback
+     * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback
      * if a TouchSpotController with the same display is removed.
      */
-    mContext.addAnimationCallback(-1, func);
+    mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func);
 }
 
 } // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 00dc085..dc7e8ca 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -47,7 +47,7 @@
     void move(float deltaX, float deltaY);
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
-    int32_t getDisplayId() const;
+    ui::LogicalDisplayId getDisplayId() const;
     void fade(PointerControllerInterface::Transition transition);
     void unfade(PointerControllerInterface::Transition transition);
     void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f97992f..cca1b07 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -142,7 +142,7 @@
 }
 
 void PointerController::move(float deltaX, float deltaY) {
-    const int32_t displayId = mCursorController.getDisplayId();
+    const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     vec2 transformed;
     {
         std::scoped_lock lock(getLock());
@@ -153,7 +153,7 @@
 }
 
 void PointerController::setPosition(float x, float y) {
-    const int32_t displayId = mCursorController.getDisplayId();
+    const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     vec2 transformed;
     {
         std::scoped_lock lock(getLock());
@@ -164,7 +164,7 @@
 }
 
 FloatPoint PointerController::getPosition() const {
-    const int32_t displayId = mCursorController.getDisplayId();
+    const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     const auto p = mCursorController.getPosition();
     {
         std::scoped_lock lock(getLock());
@@ -173,7 +173,7 @@
     }
 }
 
-int32_t PointerController::getDisplayId() const {
+ui::LogicalDisplayId PointerController::getDisplayId() const {
     return mCursorController.getDisplayId();
 }
 
@@ -202,7 +202,7 @@
 }
 
 void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                                 BitSet32 spotIdBits, int32_t displayId) {
+                                 BitSet32 spotIdBits, ui::LogicalDisplayId displayId) {
     std::scoped_lock lock(getLock());
     std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
     const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -286,7 +286,7 @@
     mCursorController.setCustomPointerIcon(icon);
 }
 
-void PointerController::setSkipScreenshot(int32_t displayId, bool skip) {
+void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
     std::scoped_lock lock(getLock());
     if (skip) {
         mLocked.displaysToSkipScreenshot.insert(displayId);
@@ -300,14 +300,14 @@
 }
 
 void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) {
-    std::unordered_set<int32_t> displayIdSet;
+    std::unordered_set<ui::LogicalDisplayId> displayIdSet;
     for (const DisplayViewport& viewport : viewports) {
         displayIdSet.insert(viewport.displayId);
     }
 
     std::scoped_lock lock(getLock());
     for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
-        int32_t displayId = it->first;
+        ui::LogicalDisplayId displayId = it->first;
         if (!displayIdSet.count(displayId)) {
             /*
              * Ensures that an in-progress animation won't dereference
@@ -326,7 +326,8 @@
     mLocked.mDisplayInfos = displayInfo;
 }
 
-const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+const ui::Transform& PointerController::getTransformForDisplayLocked(
+        ui::LogicalDisplayId displayId) const {
     const auto& di = mLocked.mDisplayInfos;
     auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
         return info.displayId == displayId;
@@ -339,7 +340,8 @@
     std::scoped_lock lock(getLock());
     dump += StringPrintf(INDENT2 "Presentation: %s\n",
                          ftl::enum_string(mLocked.presentation).c_str());
-    dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+    dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n",
+                         mLocked.pointerDisplayId.toString().c_str());
     dump += StringPrintf(INDENT2 "Viewports:\n");
     for (const auto& info : mLocked.mDisplayInfos) {
         info.dump(dump, INDENT3);
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index eaf34d5..70e5c24 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -55,18 +55,18 @@
     void move(float deltaX, float deltaY) override;
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
-    int32_t getDisplayId() const override;
+    ui::LogicalDisplayId getDisplayId() const override;
     void fade(Transition transition) override;
     void unfade(Transition transition) override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
     void setPresentation(Presentation presentation) override;
     void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits, int32_t displayId) override;
+                  BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override;
     void clearSpots() override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
-    void setSkipScreenshot(int32_t displayId, bool skip) override;
+    void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
 
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
@@ -109,11 +109,11 @@
 
     struct Locked {
         Presentation presentation;
-        int32_t pointerDisplayId = ADISPLAY_ID_NONE;
+        ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE;
 
         std::vector<gui::DisplayInfo> mDisplayInfos;
-        std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
-        std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot;
+        std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers;
+        std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot;
     } mLocked GUARDED_BY(getLock());
 
     class DisplayInfoListener : public gui::WindowInfosListener {
@@ -132,7 +132,8 @@
     sp<DisplayInfoListener> mDisplayInfoListener;
     const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
 
-    const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
+    const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const
+            REQUIRES(getLock());
 
     void clearSpotsLocked() REQUIRES(getLock());
 };
@@ -148,7 +149,7 @@
     void setPresentation(Presentation) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void clearSpots() override {
@@ -176,7 +177,7 @@
     FloatPoint getPosition() const override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    int32_t getDisplayId() const override {
+    ui::LogicalDisplayId getDisplayId() const override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void fade(Transition) override {
@@ -212,7 +213,7 @@
     void setPresentation(Presentation) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void clearSpots() override {
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index 15c3517..747eb8e 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -138,12 +138,12 @@
     return 1; // keep the callback
 }
 
-void PointerControllerContext::addAnimationCallback(int32_t displayId,
+void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId,
                                                     std::function<bool(nsecs_t)> callback) {
     mAnimator.addCallback(displayId, callback);
 }
 
-void PointerControllerContext::removeAnimationCallback(int32_t displayId) {
+void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) {
     mAnimator.removeCallback(displayId);
 }
 
@@ -161,14 +161,14 @@
     }
 }
 
-void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId,
+void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId,
                                                             std::function<bool(nsecs_t)> callback) {
     std::scoped_lock lock(mLock);
     mLocked.callbacks[displayId] = callback;
     startAnimationLocked();
 }
 
-void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) {
+void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) {
     std::scoped_lock lock(mLock);
     auto it = mLocked.callbacks.find(displayId);
     if (it == mLocked.callbacks.end()) {
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index e893c49..d422148 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -72,12 +72,13 @@
     virtual ~PointerControllerPolicyInterface() {}
 
 public:
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
+    virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0;
+    virtual void loadPointerResources(PointerResources* outResources,
+                                      ui::LogicalDisplayId displayId) = 0;
     virtual void loadAdditionalMouseResources(
             std::map<PointerIconStyle, SpriteIcon>* outResources,
             std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
-            int32_t displayId) = 0;
+            ui::LogicalDisplayId displayId) = 0;
     virtual PointerIconStyle getDefaultPointerIconId() = 0;
     virtual PointerIconStyle getDefaultStylusIconId() = 0;
     virtual PointerIconStyle getCustomPointerIconId() = 0;
@@ -102,7 +103,7 @@
 
     nsecs_t getAnimationTime();
 
-    void clearSpotsByDisplay(int32_t displayId);
+    void clearSpotsByDisplay(ui::LogicalDisplayId displayId);
 
     void setHandlerController(std::shared_ptr<PointerController> controller);
     void setCallbackController(std::shared_ptr<PointerController> controller);
@@ -112,8 +113,9 @@
 
     void handleDisplayEvents();
 
-    void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
-    void removeAnimationCallback(int32_t displayId);
+    void addAnimationCallback(ui::LogicalDisplayId displayId,
+                              std::function<bool(nsecs_t)> callback);
+    void removeAnimationCallback(ui::LogicalDisplayId displayId);
 
     class MessageHandler : public virtual android::MessageHandler {
     public:
@@ -136,8 +138,8 @@
     public:
         PointerAnimator(PointerControllerContext& context);
 
-        void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
-        void removeCallback(int32_t displayId);
+        void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback);
+        void removeCallback(ui::LogicalDisplayId displayId);
         void handleVsyncEvents();
         nsecs_t getAnimationTimeLocked();
 
@@ -148,7 +150,7 @@
             bool animationPending{false};
             nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)};
 
-            std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks;
+            std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks;
         } mLocked GUARDED_BY(mLock);
 
         DisplayEventReceiver mDisplayEventReceiver;
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 0baa929..af49939 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -19,9 +19,9 @@
 
 #include "SpriteController.h"
 
-#include <log/log.h>
-#include <utils/String8.h>
+#include <android-base/logging.h>
 #include <gui/Surface.h>
+#include <utils/String8.h>
 
 namespace android {
 
@@ -340,13 +340,14 @@
     }
 }
 
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId,
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
+                                                   ui::LogicalDisplayId displayId,
                                                    bool hideOnMirrored) {
     ensureSurfaceComposerClient();
 
     const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
     if (parent == nullptr) {
-        ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
+        LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId;
     }
 
     int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow;
@@ -475,7 +476,7 @@
     }
 }
 
-void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) {
     AutoMutex _l(mController.mLock);
 
     if (mLocked.state.displayId != displayId) {
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index fdb15506..070c90c 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -95,7 +95,7 @@
     virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
 
     /* Sets the id of the display where the sprite should be shown. */
-    virtual void setDisplayId(int32_t displayId) = 0;
+    virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0;
 
     /* Sets the flag to hide sprite on mirrored displays.
      * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */
@@ -115,7 +115,7 @@
  */
 class SpriteController {
 public:
-    using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
+    using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>;
     SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
     SpriteController(const SpriteController&) = delete;
     SpriteController& operator=(const SpriteController&) = delete;
@@ -174,7 +174,7 @@
         int32_t layer{0};
         float alpha{1.0f};
         SpriteTransformationMatrix transformationMatrix;
-        int32_t displayId{ADISPLAY_ID_DEFAULT};
+        ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT};
 
         sp<SurfaceControl> surfaceControl;
         int32_t surfaceWidth{0};
@@ -208,7 +208,7 @@
         virtual void setLayer(int32_t layer);
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
-        virtual void setDisplayId(int32_t displayId);
+        virtual void setDisplayId(ui::LogicalDisplayId displayId);
         virtual void setSkipScreenshot(bool skip);
 
         inline const SpriteState& getStateLocked() const {
@@ -273,7 +273,7 @@
     void doDisposeSurfaces();
 
     void ensureSurfaceComposerClient();
-    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId,
+    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId,
                                      bool hideOnMirrored);
 };
 
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 530d541..7462481 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -40,7 +40,7 @@
 // --- Spot ---
 
 void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
-                                             int32_t displayId, bool skipScreenshot) {
+                                             ui::LogicalDisplayId displayId, bool skipScreenshot) {
     sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
     sprite->setAlpha(alpha);
     sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
@@ -69,7 +69,8 @@
 
 // --- TouchSpotController ---
 
-TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
+TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId,
+                                         PointerControllerContext& context)
       : mDisplayId(displayId), mContext(context) {
     mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
 }
@@ -94,7 +95,7 @@
         const PointerCoords& c = spotCoords[spotIdToIndex[id]];
         ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
               c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
-              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
+              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id);
     }
 #endif
 
@@ -274,7 +275,7 @@
     out += prefix;
     out += "SpotController:\n";
     out += prefix;
-    StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+    StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str());
     std::scoped_lock lock(mLock);
     out += prefix;
     StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 608653c..ac37fa4 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -29,7 +29,7 @@
  */
 class TouchSpotController {
 public:
-    TouchSpotController(int32_t displayId, PointerControllerContext& context);
+    TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context);
     ~TouchSpotController();
     void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
                   BitSet32 spotIdBits, bool skipScreenshot);
@@ -59,7 +59,7 @@
                 y(0.0f),
                 mLastIcon(nullptr) {}
 
-        void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId,
+        void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId,
                           bool skipScreenshot);
         void dump(std::string& out, const char* prefix = "") const;
 
@@ -67,7 +67,7 @@
         const SpriteIcon* mLastIcon;
     };
 
-    int32_t mDisplayId;
+    ui::LogicalDisplayId mDisplayId;
 
     mutable std::mutex mLock;
 
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 3bc0e24..7a13380 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -52,12 +52,13 @@
 
 class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
 public:
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
+    virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override;
+    virtual void loadPointerResources(PointerResources* outResources,
+                                      ui::LogicalDisplayId displayId) override;
     virtual void loadAdditionalMouseResources(
             std::map<PointerIconStyle, SpriteIcon>* outResources,
             std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
-            int32_t displayId) override;
+            ui::LogicalDisplayId displayId) override;
     virtual PointerIconStyle getDefaultPointerIconId() override;
     virtual PointerIconStyle getDefaultStylusIconId() override;
     virtual PointerIconStyle getCustomPointerIconId() override;
@@ -73,13 +74,13 @@
     bool additionalMouseResourcesLoaded{false};
 };
 
-void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
+void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) {
     loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
     pointerIconLoaded = true;
 }
 
 void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
-        int32_t) {
+                                                                ui::LogicalDisplayId) {
     loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
     loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
     loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
@@ -88,7 +89,7 @@
 
 void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
         std::map<PointerIconStyle, SpriteIcon>* outResources,
-        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) {
+        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) {
     SpriteIcon icon;
     PointerAnimation anim;
 
@@ -165,7 +166,7 @@
     PointerControllerTest();
     ~PointerControllerTest();
 
-    void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
+    void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT);
 
     sp<MockSprite> mPointerSprite;
     sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -204,7 +205,7 @@
     mThread.join();
 }
 
-void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
+void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) {
     DisplayViewport viewport;
     viewport.displayId = displayId;
     viewport.logicalRight = 1600;
@@ -334,23 +335,23 @@
 
     // Update spots to sync state with sprite
     mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
-                                 ADISPLAY_ID_DEFAULT);
+                                 ui::ADISPLAY_ID_DEFAULT);
     testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 
     // Marking the display to skip screenshot should update sprite as well
-    mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true);
+    mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true);
     EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
 
     // Update spots to sync state with sprite
     mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
-                                 ADISPLAY_ID_DEFAULT);
+                                 ui::ADISPLAY_ID_DEFAULT);
     testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 
     // Reset flag and verify again
-    mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false);
+    mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false);
     EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
     mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
-                                 ADISPLAY_ID_DEFAULT);
+                                 ui::ADISPLAY_ID_DEFAULT);
     testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 }
 
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
index 0867221..21628fb 100644
--- a/libs/input/tests/mocks/MockSprite.h
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -33,7 +33,7 @@
     MOCK_METHOD(void, setLayer, (int32_t), (override));
     MOCK_METHOD(void, setAlpha, (float), (override));
     MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
-    MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+    MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override));
     MOCK_METHOD(void, setSkipScreenshot, (bool), (override));
 };
 
diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h
index 62f1d65..9ef6b7c 100644
--- a/libs/input/tests/mocks/MockSpriteController.h
+++ b/libs/input/tests/mocks/MockSpriteController.h
@@ -27,7 +27,7 @@
 
 public:
     MockSpriteController(sp<Looper> looper)
-          : SpriteController(looper, 0, [](int) { return nullptr; }) {}
+          : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {}
     ~MockSpriteController() {}
 
     MOCK_METHOD(sp<Sprite>, createSprite, (), (override));
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index d969d1c..2e9b7b4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -446,6 +446,9 @@
         // Returns InstallUserActionRequired stage if install details could be successfully
         // computed, else it returns InstallAborted.
         val confirmationSnippet: InstallStage = generateConfirmationSnippet()
+        if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) {
+            return confirmationSnippet
+        }
 
         val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!)
         return if (sessionId == SessionInfo.INVALID_ID &&
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 7c76ea1..221e8f5 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -38,7 +38,7 @@
     <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
 
     <!-- Dialog background color. -->
-    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
 
     <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
 
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2a6499a..dc2d3dc 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -38,7 +38,7 @@
     <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
 
     <!-- Dialog background color. -->
-    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
 
     <!-- Material next track outline color-->
     <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 3dffb27..8917412 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -10,8 +10,10 @@
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -37,12 +39,9 @@
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
-import com.google.common.collect.ImmutableSet;
-
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
-import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -56,8 +55,6 @@
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
-    private static final Set<String> EXCLUSIVE_MANAGERS =
-            ImmutableSet.of("com.google.android.gms.dck");
 
     private static ErrorListener sErrorListener;
 
@@ -740,14 +737,13 @@
 
     /**
      * Returns the BluetoothDevice's exclusive manager ({@link
-     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
-     * set, otherwise null.
+     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists, otherwise null.
      */
     @Nullable
-    private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
-        byte[] exclusiveManagerNameBytes =
+    private static String getExclusiveManager(BluetoothDevice bluetoothDevice) {
+        byte[] exclusiveManagerBytes =
                 bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
-        if (exclusiveManagerNameBytes == null) {
+        if (exclusiveManagerBytes == null) {
             Log.d(
                     TAG,
                     "Bluetooth device "
@@ -755,47 +751,46 @@
                             + " doesn't have exclusive manager");
             return null;
         }
-        String exclusiveManagerName = new String(exclusiveManagerNameBytes);
-        return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
+        return new String(exclusiveManagerBytes);
     }
 
-    /** Checks if given package is installed */
-    private static boolean isPackageInstalled(Context context, String packageName) {
+    /** Checks if given package is installed and enabled */
+    private static boolean isPackageInstalledAndEnabled(Context context, String packageName) {
         PackageManager packageManager = context.getPackageManager();
         try {
-            packageManager.getPackageInfo(packageName, 0);
-            return true;
+            ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+            return appInfo.enabled;
         } catch (PackageManager.NameNotFoundException e) {
-            Log.d(TAG, "Package " + packageName + " is not installed");
+            Log.d(TAG, "Package " + packageName + " is not installed/enabled");
         }
         return false;
     }
 
     /**
      * A BluetoothDevice is exclusively managed if 1) it has field {@link
-     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
-     * in the allowlist. 3) the exclusive manager app is installed.
+     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app is
+     * installed and enabled.
      */
     public static boolean isExclusivelyManagedBluetoothDevice(
             @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
-        String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+        String exclusiveManagerName = getExclusiveManager(bluetoothDevice);
         if (exclusiveManagerName == null) {
             return false;
         }
-        if (!isPackageInstalled(context, exclusiveManagerName)) {
+
+        ComponentName exclusiveManagerComponent =
+                ComponentName.unflattenFromString(exclusiveManagerName);
+        String exclusiveManagerPackage = exclusiveManagerComponent != null
+                ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName;
+
+        if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) {
             return false;
         } else {
-            Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+            Log.d(TAG, "Found exclusively managed app " + exclusiveManagerPackage);
             return true;
         }
     }
 
-    /** Return the allowlist for exclusive manager names. */
-    @NonNull
-    public static Set<String> getExclusiveManagers() {
-        return EXCLUSIVE_MANAGERS;
-    }
-
     /**
      * Get CSIP group id for {@link CachedBluetoothDevice}.
      *
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index f197f9e..7a2818d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -28,7 +28,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
-import android.content.pm.PackageInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -80,7 +80,8 @@
     private static final String CONTROL_METADATA =
             "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
                     + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
-    private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
+    private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
+    private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
 
     @Before
     public void setUp() {
@@ -399,7 +400,7 @@
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+    public void isExclusivelyManaged_hasNoManager_returnFalse() {
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
                 null);
 
@@ -408,45 +409,85 @@
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+    public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse()
+            throws Exception {
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+                TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+                .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
 
         assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
                 mBluetoothDevice)).isEqualTo(false);
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+    public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse()
             throws Exception {
-        final String exclusiveManagerName =
-                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
-                        FAKE_EXCLUSIVE_MANAGER_NAME);
-
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                exclusiveManagerName.getBytes());
+                TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
-                exclusiveManagerName, 0);
+        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+                .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
 
         assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
-                mBluetoothDevice)).isEqualTo(false);
+            mBluetoothDevice)).isEqualTo(false);
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
-            throws Exception {
-        final String exclusiveManagerName =
-                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
-                        FAKE_EXCLUSIVE_MANAGER_NAME);
-
-        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                exclusiveManagerName.getBytes());
+    public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse()
+             throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.enabled = false;
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+        doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
 
         assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
-                mBluetoothDevice)).isEqualTo(true);
+            mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManaged_hasComponentName_packageNotEnabled_returnFalse()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.enabled = false;
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+            mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue()
+            throws Exception {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+            mBluetoothDevice)).isEqualTo(true);
+    }
+
+    @Test
+    public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue()
+            throws Exception {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+            mBluetoothDevice)).isEqualTo(true);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 461b6b3..70ce202 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2949,9 +2949,6 @@
         dumpSetting(s, p,
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                 SystemSettingsProto.Screen.AUTO_BRIGHTNESS_ADJ);
-        dumpSetting(s, p,
-                Settings.System.SCREEN_BRIGHTNESS_FLOAT,
-                SystemSettingsProto.Screen.BRIGHTNESS_FLOAT);
         p.end(screenToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c891dfc..92167ee 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -935,7 +935,6 @@
                         Settings.System.VOLUME_VOICE, // deprecated since API 2?
                         Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                         Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
-                        Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                         Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
                         Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
                         Settings.System.WEAR_TTS_PREWARM_ENABLED,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 21881f6..80398cd 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -550,6 +550,13 @@
 }
 
 flag {
+    name: "enable_contextual_tip_for_mute_volume"
+    namespace: "systemui"
+    description: "Enables the contextual tip for muting the volume."
+    bug: "337737048"
+}
+
+flag {
    name: "disable_contextual_tips_frequency_check"
    description: "Disables frequency capping check for contextual tips."
    namespace: "systemui"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1e60b98..d4660fa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -44,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityTransitionAnimator"
@@ -52,14 +53,19 @@
  * A class that allows activities to be started in a seamless way from a view that is transforming
  * nicely into the starting window.
  */
-class ActivityTransitionAnimator(
+class ActivityTransitionAnimator
+@JvmOverloads
+constructor(
+    /** The executor that runs on the main thread. */
+    private val mainExecutor: Executor,
+
     /** The animator used when animating a View into an app. */
-    private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+    private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
 
     /** The animator used when animating a Dialog into an app. */
     // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
     // TIMINGS.contentBeforeFadeOutDuration.
-    private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+    private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
 
     /**
      * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -100,10 +106,6 @@
         // TODO(b/288507023): Remove this flag.
         @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE
 
-        private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
-        private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
-            TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
-
         /** Durations & interpolators for the navigation bar fading in & out. */
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
@@ -121,6 +123,14 @@
          * cancelled by WM.
          */
         private const val LONG_TRANSITION_TIMEOUT = 5_000L
+
+        private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
+            return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
+        }
+
+        private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
+            return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS)
+        }
     }
 
     /**
@@ -257,9 +267,7 @@
 
     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            this.transitionContainer.context.mainExecutor.execute {
-                callOnIntentStartedOnMainThread(willAnimate)
-            }
+            mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) }
         } else {
             if (DEBUG_TRANSITION_ANIMATION) {
                 Log.d(
@@ -479,12 +487,10 @@
         controller: Controller,
         callback: Callback,
         /** The animator to use to animate the window transition. */
-        transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+        transitionAnimator: TransitionAnimator,
         /** Listener for animation lifecycle events. */
         listener: Listener? = null
     ) : IRemoteAnimationRunner.Stub() {
-        private val context = controller.transitionContainer.context
-
         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
         // etc.) are possible. So we need to make sure we drop any references that might
         // transitively cause leaks when we're done with animation.
@@ -493,11 +499,12 @@
         init {
             delegate =
                 AnimationDelegate(
+                    mainExecutor,
                     controller,
                     callback,
                     DelegatingAnimationCompletionListener(listener, this::dispose),
                     transitionAnimator,
-                    disableWmTimeout
+                    disableWmTimeout,
                 )
         }
 
@@ -510,7 +517,7 @@
             finishedCallback: IRemoteAnimationFinishedCallback?
         ) {
             val delegate = delegate
-            context.mainExecutor.execute {
+            mainExecutor.execute {
                 if (delegate == null) {
                     Log.i(TAG, "onAnimationStart called after completion")
                     // Animation started too late and timed out already. We need to still
@@ -525,7 +532,7 @@
         @BinderThread
         override fun onAnimationCancelled() {
             val delegate = delegate
-            context.mainExecutor.execute {
+            mainExecutor.execute {
                 delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
                 delegate?.onAnimationCancelled()
             }
@@ -535,19 +542,21 @@
         fun dispose() {
             // Drop references to animation controller once we're done with the animation
             // to avoid leaking.
-            context.mainExecutor.execute { delegate = null }
+            mainExecutor.execute { delegate = null }
         }
     }
 
     class AnimationDelegate
     @JvmOverloads
     constructor(
+        private val mainExecutor: Executor,
         private val controller: Controller,
         private val callback: Callback,
         /** Listener for animation lifecycle events. */
         private val listener: Listener? = null,
         /** The animator to use to animate the window transition. */
-        private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+        private val transitionAnimator: TransitionAnimator =
+            defaultTransitionAnimator(mainExecutor),
 
         /**
          * Whether we should disable the WindowManager timeout. This should be set to true in tests
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index b89ebfc..f5d01d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -37,6 +37,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.util.maybeForceFullscreen
 import com.android.systemui.util.registerAnimationOnBackInvoked
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogTransitionAnimator"
@@ -55,10 +56,16 @@
 class DialogTransitionAnimator
 @JvmOverloads
 constructor(
+    private val mainExecutor: Executor,
     private val callback: Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val featureFlags: AnimationFeatureFlags,
-    private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
+    private val transitionAnimator: TransitionAnimator =
+        TransitionAnimator(
+            mainExecutor,
+            TIMINGS,
+            INTERPOLATORS,
+        ),
     private val isForTesting: Boolean = false,
 ) {
     private companion object {
@@ -937,24 +944,9 @@
                 }
 
                 override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
-                    // onLaunchAnimationEnd is called by an Animator at the end of the animation,
-                    // on a Choreographer animation tick. The following calls will move the animated
-                    // content from the dialog overlay back to its original position, and this
-                    // change must be reflected in the next frame given that we then sync the next
-                    // frame of both the content and dialog ViewRoots. However, in case that content
-                    // is rendered by Compose, whose compositions are also scheduled on a
-                    // Choreographer frame, any state change made *right now* won't be reflected in
-                    // the next frame given that a Choreographer frame can't schedule another and
-                    // have it happen in the same frame. So we post the forwarded calls to
-                    // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
-                    // that the move of the content back to its original window will be reflected in
-                    // the next frame right after [onLaunchAnimationEnd] is called.
-                    dialog.context.mainExecutor.execute {
-                        startController.onTransitionAnimationEnd(isExpandingFullyAbove)
-                        endController.onTransitionAnimationEnd(isExpandingFullyAbove)
-
-                        onLaunchAnimationEnd()
-                    }
+                    startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    onLaunchAnimationEnd()
                 }
 
                 override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 679c969..cc55df1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,12 +31,17 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.animation.Interpolators.LINEAR
 import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 private const val TAG = "TransitionAnimator"
 
 /** A base class to animate a window (activity or dialog) launch to or return from a view . */
-class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+class TransitionAnimator(
+    private val mainExecutor: Executor,
+    private val timings: Timings,
+    private val interpolators: Interpolators,
+) {
     companion object {
         internal const val DEBUG = false
         private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
@@ -351,11 +356,27 @@
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
-                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
-                    transitionContainerOverlay.remove(windowBackgroundLayer)
 
-                    if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
-                        openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+                    // onAnimationEnd is called at the end of the animation, on a Choreographer
+                    // animation tick. During dialog launches, the following calls will move the
+                    // animated content from the dialog overlay back to its original position, and
+                    // this change must be reflected in the next frame given that we then sync the
+                    // next frame of both the content and dialog ViewRoots. During SysUI activity
+                    // launches, we will instantly collapse the shade at the end of the transition.
+                    // However, if those are rendered by Compose, whose compositions are also
+                    // scheduled on a Choreographer frame, any state change made *right now* won't
+                    // be reflected in the next frame given that a Choreographer frame can't
+                    // schedule another and have it happen in the same frame. So we post the
+                    // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
+                    // leaving this Choreographer frame, ensuring that any state change applied by
+                    // onTransitionAnimationEnd() will be reflected in the same frame.
+                    mainExecutor.execute {
+                        controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        transitionContainerOverlay.remove(windowBackgroundLayer)
+
+                        if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+                            openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+                        }
                     }
                 }
             }
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 bca8fde..30b6c6c 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
@@ -36,6 +36,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -68,6 +69,8 @@
 import com.android.compose.modifiers.thenIf
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 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
@@ -152,6 +155,8 @@
     modifier: Modifier = Modifier,
     shadeSession: SaveableSession,
 ) {
+    val cutoutLocation = LocalDisplayCutout.current.location
+
     val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
     val contentAlpha by
         animateFloatAsState(
@@ -183,6 +188,9 @@
                     // scene (and not the one under it) during a scene transition.
                     Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                 }
+                .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+                    Modifier.displayCutoutPadding()
+                },
     ) {
         val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
         val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 36b60d6..10fe0cab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -67,6 +68,8 @@
 import com.android.compose.modifiers.padding
 import com.android.compose.modifiers.thenIf
 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.LocalScreenCornerRadius
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
@@ -208,6 +211,8 @@
     modifier: Modifier = Modifier,
     shadeSession: SaveableSession,
 ) {
+    val cutoutLocation = LocalDisplayCutout.current.location
+
     val maxNotifScrimTop = remember { mutableStateOf(0f) }
     val tileSquishiness by
         animateSceneFloatAsState(
@@ -243,9 +248,15 @@
                         Column(
                             horizontalAlignment = Alignment.CenterHorizontally,
                             modifier =
-                                Modifier.fillMaxWidth().thenIf(isClickable) {
-                                    Modifier.clickable(onClick = { viewModel.onContentClicked() })
-                                }
+                                Modifier.fillMaxWidth()
+                                    .thenIf(isClickable) {
+                                        Modifier.clickable(
+                                            onClick = { viewModel.onContentClicked() }
+                                        )
+                                    }
+                                    .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+                                        Modifier.displayCutoutPadding()
+                                    },
                         ) {
                             CollapsedShadeHeader(
                                 viewModel = viewModel.shadeHeaderViewModel,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index f539a23..bdeab79 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,16 +28,16 @@
 import android.util.AttributeSet
 import android.util.MathUtils.constrainedMap
 import android.util.TypedValue
-import android.view.View.MeasureSpec.EXACTLY
 import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
 import android.widget.TextView
 import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
-import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
@@ -47,11 +47,13 @@
 import kotlin.math.min
 
 /**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
+ * Displays the time with the hour positioned above the minutes (ie: 09 above 30 is 9:30). The
+ * time's text color is a gradient that changes its colors based on its controller.
  */
 @SuppressLint("AppCompatCustomView")
-class AnimatableClockView @JvmOverloads constructor(
+class AnimatableClockView
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
@@ -63,7 +65,9 @@
         get() = field ?: DEFAULT_LOGGER
     var messageBuffer: MessageBuffer
         get() = logger.buffer
-        set(value) { logger = Logger(value, TAG) }
+        set(value) {
+            logger = Logger(value, TAG)
+        }
 
     var hasCustomPositionUpdatedAnimation: Boolean = false
     var migratedClocks: Boolean = false
@@ -77,16 +81,13 @@
     private var format: CharSequence? = null
     private var descFormat: CharSequence? = null
 
-    @ColorInt
-    private var dozingColor = 0
-
-    @ColorInt
-    private var lockScreenColor = 0
+    @ColorInt private var dozingColor = 0
+    @ColorInt private var lockScreenColor = 0
 
     private var lineSpacingScale = 1f
     private val chargeAnimationDelay: Int
     private var textAnimator: TextAnimator? = null
-    private var onTextAnimatorInitialized: Runnable? = null
+    private var onTextAnimatorInitialized: ((TextAnimator) -> Unit)? = null
 
     private var translateForCenterAnimation = false
     private val parentWidth: Int
@@ -94,9 +95,11 @@
 
     // last text size which is not constrained by view height
     private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
-    @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
-        { layout, invalidateCb ->
-            TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
+
+    @VisibleForTesting
+    var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
+        TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb)
+    }
 
     // Used by screenshot tests to provide stability
     @VisibleForTesting var isAnimationEnabled: Boolean = true
@@ -109,40 +112,55 @@
         get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
 
     /**
-     * The number of pixels below the baseline. For fonts that support languages such as
-     * Burmese, this space can be significant and should be accounted for when computing layout.
+     * The number of pixels below the baseline. For fonts that support languages such as Burmese,
+     * this space can be significant and should be accounted for when computing layout.
      */
-    val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+    val bottom: Float
+        get() = paint?.fontMetrics?.bottom ?: 0f
 
     init {
-        val animatableClockViewAttributes = context.obtainStyledAttributes(
-            attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
-        )
+        val animatableClockViewAttributes =
+            context.obtainStyledAttributes(
+                attrs,
+                R.styleable.AnimatableClockView,
+                defStyleAttr,
+                defStyleRes
+            )
 
         try {
-            dozingWeightInternal = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_dozeWeight,
-                /* default = */ 100
-            )
-            lockScreenWeightInternal = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_lockScreenWeight,
-                /* default = */ 300
-            )
-            chargeAnimationDelay = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200
-            )
+            dozingWeightInternal =
+                animatableClockViewAttributes.getInt(
+                    R.styleable.AnimatableClockView_dozeWeight,
+                    /* default = */ 100
+                )
+            lockScreenWeightInternal =
+                animatableClockViewAttributes.getInt(
+                    R.styleable.AnimatableClockView_lockScreenWeight,
+                    /* default = */ 300
+                )
+            chargeAnimationDelay =
+                animatableClockViewAttributes.getInt(
+                    R.styleable.AnimatableClockView_chargeAnimationDelay,
+                    /* default = */ 200
+                )
         } finally {
             animatableClockViewAttributes.recycle()
         }
 
-        val textViewAttributes = context.obtainStyledAttributes(
-            attrs, android.R.styleable.TextView,
-            defStyleAttr, defStyleRes
-        )
+        val textViewAttributes =
+            context.obtainStyledAttributes(
+                attrs,
+                android.R.styleable.TextView,
+                defStyleAttr,
+                defStyleRes
+            )
 
         try {
-            isSingleLineInternal = textViewAttributes.getBoolean(
-                android.R.styleable.TextView_singleLine, /* default = */ false)
+            isSingleLineInternal =
+                textViewAttributes.getBoolean(
+                    android.R.styleable.TextView_singleLine,
+                    /* default = */ false
+                )
         } finally {
             textViewAttributes.recycle()
         }
@@ -156,9 +174,7 @@
         refreshFormat()
     }
 
-    /**
-     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
-     */
+    /** Whether to use a bolded version based on the user specified fontWeightAdjustment. */
     fun useBoldedVersion(): Boolean {
         // "Bold text" fontWeightAdjustment is 300.
         return resources.configuration.fontWeightAdjustment > 100
@@ -169,25 +185,30 @@
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
         logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
-        // Setting text actually triggers a layout pass (because the text view is set to
-        // wrap_content width and TextView always relayouts for this). Avoid needless
-        // relayout if the text didn't actually change.
-        if (!TextUtils.equals(text, formattedText)) {
-            text = formattedText
-            logger.d({ "refreshTime: done setting new time text to: $str1" }) {
-                str1 = formattedText?.toString()
-            }
-            // Because the TextLayout may mutate under the hood as a result of the new text, we
-            // notify the TextAnimator that it may have changed and request a measure/layout. A
-            // crash will occur on the next invocation of setTextStyle if the layout is mutated
-            // without being notified TextInterpolator being notified.
-            if (layout != null) {
-                textAnimator?.updateLayout(layout)
-                logger.d("refreshTime: done updating textAnimator layout")
-            }
-            requestLayout()
-            logger.d("refreshTime: after requestLayout")
+
+        // Setting text actually triggers a layout pass in TextView (because the text view is set to
+        // wrap_content width and TextView always relayouts for this). This avoids needless relayout
+        // if the text didn't actually change.
+        if (TextUtils.equals(text, formattedText)) {
+            return
         }
+
+        text = formattedText
+        logger.d({ "refreshTime: done setting new time text to: $str1" }) {
+            str1 = formattedText?.toString()
+        }
+
+        // Because the TextLayout may mutate under the hood as a result of the new text, we notify
+        // the TextAnimator that it may have changed and request a measure/layout. A crash will
+        // occur on the next invocation of setTextStyle if the layout is mutated without being
+        // notified TextInterpolator being notified.
+        if (layout != null) {
+            textAnimator?.updateLayout(layout)
+            logger.d("refreshTime: done updating textAnimator layout")
+        }
+
+        requestLayout()
+        logger.d("refreshTime: after requestLayout")
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
@@ -206,19 +227,27 @@
     @SuppressLint("DrawAllocation")
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         logger.d("onMeasure")
-        if (migratedClocks && !isSingleLineInternal &&
-                MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+
+        if (
+            migratedClocks &&
+                !isSingleLineInternal &&
+                MeasureSpec.getMode(heightMeasureSpec) == EXACTLY
+        ) {
             // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
-            super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                    min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
+            super.setTextSize(
+                TypedValue.COMPLEX_UNIT_PX,
+                min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)
+            )
         }
 
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         val animator = textAnimator
         if (animator == null) {
-            textAnimator = textAnimatorFactory(layout, ::invalidate)
-            onTextAnimatorInitialized?.run()
-            onTextAnimatorInitialized = null
+            textAnimator =
+                textAnimatorFactory(layout, ::invalidate)?.also {
+                    onTextAnimatorInitialized?.invoke(it)
+                    onTextAnimatorInitialized = null
+                }
         } else {
             animator.updateLayout(layout)
         }
@@ -243,15 +272,13 @@
             canvas.translate(parentWidth / 4f, 0f)
         }
 
-        logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
+        logger.d({ "onDraw($str1)" }) { str1 = text.toString() }
         // intentionally doesn't call super.onDraw here or else the text will be rendered twice
         textAnimator?.draw(canvas)
         canvas.restore()
     }
 
     override fun invalidate() {
-        @Suppress("UNNECESSARY_SAFE_CALL")
-        // logger won't be initialized when called by TextView's constructor
         logger.d("invalidate")
         super.invalidate()
     }
@@ -325,6 +352,7 @@
         if (textAnimator == null) {
             return
         }
+
         logger.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
@@ -348,10 +376,11 @@
     }
 
     fun animateCharge(isDozing: () -> Boolean) {
+        // Skip charge animation if dozing animation is already playing.
         if (textAnimator == null || textAnimator!!.isRunning()) {
-            // Skip charge animation if dozing animation is already playing.
             return
         }
+
         logger.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
@@ -409,10 +438,9 @@
 
     /**
      * Set text style with an optional animation.
-     *
-     * By passing -1 to weight, the view preserves its current weight.
-     * By passing -1 to textSize, the view preserves its current text size.
-     * By passing null to color, the view preserves its current color.
+     * - By passing -1 to weight, the view preserves its current weight.
+     * - By passing -1 to textSize, the view preserves its current text size.
+     * - By passing null to color, the view preserves its current color.
      *
      * @param weight text weight.
      * @param textSize font size.
@@ -428,8 +456,8 @@
         delay: Long,
         onAnimationEnd: Runnable?
     ) {
-        if (textAnimator != null) {
-            textAnimator?.setTextStyle(
+        textAnimator?.let {
+            it.setTextStyle(
                 weight = weight,
                 textSize = textSize,
                 color = color,
@@ -439,23 +467,24 @@
                 delay = delay,
                 onAnimationEnd = onAnimationEnd
             )
-            textAnimator?.glyphFilter = glyphFilter
-        } else {
-            // when the text animator is set, update its start values
-            onTextAnimatorInitialized = Runnable {
-                textAnimator?.setTextStyle(
-                    weight = weight,
-                    textSize = textSize,
-                    color = color,
-                    animate = false,
-                    duration = duration,
-                    interpolator = interpolator,
-                    delay = delay,
-                    onAnimationEnd = onAnimationEnd
-                )
-                textAnimator?.glyphFilter = glyphFilter
-            }
+            it.glyphFilter = glyphFilter
         }
+            ?: run {
+                // when the text animator is set, update its start values
+                onTextAnimatorInitialized = { textAnimator ->
+                    textAnimator.setTextStyle(
+                        weight = weight,
+                        textSize = textSize,
+                        color = color,
+                        animate = false,
+                        duration = duration,
+                        interpolator = interpolator,
+                        delay = delay,
+                        onAnimationEnd = onAnimationEnd
+                    )
+                    textAnimator.glyphFilter = glyphFilter
+                }
+            }
     }
 
     private fun setTextStyle(
@@ -483,12 +512,13 @@
     fun refreshFormat(use24HourFormat: Boolean) {
         Patterns.update(context)
 
-        format = when {
-            isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
-            !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
-            isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
-            else -> DOUBLE_LINE_FORMAT_12_HOUR
-        }
+        format =
+            when {
+                isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+                !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+                isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+                else -> DOUBLE_LINE_FORMAT_12_HOUR
+            }
         logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
@@ -510,10 +540,10 @@
         pw.println("    time=$time")
     }
 
-    private val moveToCenterDelays
+    private val moveToCenterDelays: List<Int>
         get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
 
-    private val moveToSideDelays
+    private val moveToSideDelays: List<Int>
         get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
 
     /**
@@ -531,7 +561,7 @@
     fun offsetGlyphsForStepClockAnimation(
         clockStartLeft: Int,
         clockMoveDirection: Int,
-        moveFraction: Float
+        moveFraction: Float,
     ) {
         val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
         val currentMoveAmount = left - clockStartLeft
@@ -558,8 +588,8 @@
      *
      * @param distance is the total distance in pixels to offset the glyphs when animation
      *   completes. Negative distance means we are animating the position towards the center.
-     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1
-     *   means it finished moving.
+     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+     *   it finished moving.
      */
     fun offsetGlyphsForStepClockAnimation(
         distance: Float,
@@ -568,13 +598,17 @@
         for (i in 0 until NUM_DIGITS) {
             val dir = if (isLayoutRtl) -1 else 1
             val digitFraction =
-                getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
+                getDigitFraction(
+                    digit = i,
+                    isMovingToCenter = distance > 0,
+                    fraction = fraction,
+                )
             val moveAmountForDigit = dir * distance * digitFraction
             glyphOffsets[i] = moveAmountForDigit
 
             if (distance > 0) {
-                // If distance > 0 then we are moving from the left towards the center.
-                // We need ensure that the glyphs are offset to the initial position.
+                // If distance > 0 then we are moving from the left towards the center. We need to
+                // ensure that the glyphs are offset to the initial position.
                 glyphOffsets[i] -= dir * distance
             }
         }
@@ -582,27 +616,25 @@
     }
 
     private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
-        // The delay for the digit, in terms of fraction (i.e. the digit should not move
-        // during 0.0 - 0.1).
-        val digitInitialDelay =
-            if (isMovingToCenter) {
-                moveToCenterDelays[digit] * MOVE_DIGIT_STEP
-            } else {
-                moveToSideDelays[digit] * MOVE_DIGIT_STEP
-            }
+        // The delay for the digit, in terms of fraction.
+        // (i.e. the digit should not move during 0.0 - 0.1).
+        val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+        val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
         return MOVE_INTERPOLATOR.getInterpolation(
-                constrainedMap(
-                    0.0f,
-                    1.0f,
-                    digitInitialDelay,
-                    digitInitialDelay + AVAILABLE_ANIMATION_TIME,
-                    fraction,
-                )
+            constrainedMap(
+                /* rangeMin= */ 0.0f,
+                /* rangeMax= */ 1.0f,
+                /* valueMin= */ digitInitialDelay,
+                /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+                /* value= */ fraction,
             )
+        )
     }
 
-    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
-    // This is an optimization to ensure we only recompute the patterns when the inputs change.
+    /**
+     * DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. This
+     * is a cache optimization to ensure we only recompute the patterns when the inputs change.
+     */
     private object Patterns {
         var sClockView12: String? = null
         var sClockView24: String? = null
@@ -610,21 +642,22 @@
 
         fun update(context: Context) {
             val locale = Locale.getDefault()
-            val res = context.resources
-            val clockView12Skel = res.getString(R.string.clock_12hr_format)
-            val clockView24Skel = res.getString(R.string.clock_24hr_format)
-            val key = locale.toString() + clockView12Skel + clockView24Skel
-            if (key == sCacheKey) return
-
-            val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
-            sClockView12 = clockView12
-
-            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
-            // format.  The following code removes the AM/PM indicator if we didn't want it.
-            if (!clockView12Skel.contains("a")) {
-                sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+            val clockView12Skel = context.resources.getString(R.string.clock_12hr_format)
+            val clockView24Skel = context.resources.getString(R.string.clock_24hr_format)
+            val key = "$locale$clockView12Skel$clockView24Skel"
+            if (key == sCacheKey) {
+                return
             }
 
+            sClockView12 =
+                DateFormat.getBestDateTimePattern(locale, clockView12Skel).let {
+                    // CLDR insists on adding an AM/PM indicator even though it wasn't in the format
+                    // string. The following code removes the AM/PM indicator if we didn't want it.
+                    if (!clockView12Skel.contains("a")) {
+                        it.replace("a".toRegex(), "").trim { it <= ' ' }
+                    } else it
+                }
+
             sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
             sCacheKey = key
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3d8159e..9c9ee53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -66,7 +65,7 @@
 
             runCurrent()
 
-            Truth.assertThat(actualValue).isFalse()
+            assertThat(actualValue).isFalse()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
new file mode 100644
index 0000000..ca824cb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayRepositoryTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testUser = UserHandle.of(1)!!
+    private val testStartTime = LocalTime.MIDNIGHT
+    private val testEndTime = LocalTime.NOON
+    private val colorDisplayManager =
+        mock<ColorDisplayManager> {
+            whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+            whenever(isNightDisplayActivated).thenReturn(false)
+            whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+            whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+        }
+    private val locationController = FakeLocationController(LeakCheck())
+    private val nightDisplayListener = mock<NightDisplayListener>()
+    private val listenerBuilder =
+        mock<NightDisplayListenerModule.Builder> {
+            whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+            whenever(build()).thenReturn(nightDisplayListener)
+        }
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val secureSettings = kosmos.fakeSettings
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val userScopedColorDisplayManager =
+        mock<UserScopedService<ColorDisplayManager>> {
+            whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+        }
+
+    private val underTest =
+        NightDisplayRepository(
+            testDispatcher,
+            scope.backgroundScope,
+            globalSettings,
+            secureSettings,
+            listenerBuilder,
+            userScopedColorDisplayManager,
+            locationController,
+        )
+
+    @Before
+    fun setup() {
+        enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+    }
+
+    @Test
+    fun nightDisplayState_matchesAutoMode() =
+        scope.runTest {
+            enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+            val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+
+            runCurrent()
+
+            verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+            val callback = callbackCaptor.value
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_DISABLED)
+
+            callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+            callback.onCustomStartTimeChanged(testStartTime)
+            assertThat(lastState!!.startTime).isEqualTo(testStartTime)
+
+            callback.onCustomEndTimeChanged(testEndTime)
+            assertThat(lastState!!.endTime).isEqualTo(testEndTime)
+
+            callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+        }
+
+    @Test
+    fun nightDisplayState_matchesIsNightDisplayActivated() =
+        scope.runTest {
+            val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+            val callback = callbackCaptor.value
+            assertThat(lastState!!.isActivated)
+                .isEqualTo(colorDisplayManager.isNightDisplayActivated)
+
+            callback.onActivated(true)
+            assertThat(lastState!!.isActivated).isTrue()
+
+            callback.onActivated(false)
+            assertThat(lastState!!.isActivated).isFalse()
+        }
+
+    @Test
+    fun nightDisplayState_matchesController_initiallyCustomAutoMode() =
+        scope.runTest {
+            whenever(colorDisplayManager.nightDisplayAutoMode)
+                .thenReturn(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+        }
+
+    @Test
+    fun nightDisplayState_matchesController_initiallyTwilightAutoMode() =
+        scope.runTest {
+            whenever(colorDisplayManager.nightDisplayAutoMode)
+                .thenReturn(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+        }
+
+    @Test
+    fun nightDisplayState_matchesForceAutoMode() =
+        scope.runTest {
+            enrollInForcedNightDisplayAutoMode(false, testUser)
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.shouldForceAutoMode).isEqualTo(false)
+
+            enrollInForcedNightDisplayAutoMode(true, testUser)
+            assertThat(lastState!!.shouldForceAutoMode).isEqualTo(true)
+        }
+
+    private fun enrollInForcedNightDisplayAutoMode(enroll: Boolean, userHandle: UserHandle) {
+        globalSettings.putString(
+            Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+            if (enroll) NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+            else NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE
+        )
+        secureSettings.putIntForUser(
+            Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+            if (enroll) NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET else NIGHT_DISPLAY_AUTO_MODE_RAW_SET,
+            userHandle.identifier
+        )
+    }
+
+    private companion object {
+        const val INITIALLY_FORCE_AUTO_MODE = false
+        const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+        const val NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE = "0"
+        const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+        const val NIGHT_DISPLAY_AUTO_MODE_RAW_SET = 0
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 02d927a..f9d5073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -86,4 +86,26 @@
             )
             assertThat(isUmoOnCommunal).isFalse()
         }
+
+    @Test
+    fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
+        testScope.runTest {
+            val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+            assertThat(isUmoOnCommunal).isNull()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.OCCLUDED,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope
+            )
+            assertThat(isUmoOnCommunal).isTrue()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.OCCLUDED,
+                testScope
+            )
+
+            assertThat(isUmoOnCommunal).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9d6a66d..1f8cb8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -90,7 +90,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
     @Mock private lateinit var user: UserInfo
     @Mock private lateinit var providerInfo: AppWidgetProviderInfo
@@ -111,7 +111,7 @@
     private lateinit var underTest: CommunalViewModel
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2b3f40f..e3dd9ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -170,7 +170,7 @@
     @Test
     public void testBurnInProtectionStopsWhenContentViewDetached() {
         mController.onViewDetached();
-        verify(mHandler).removeCallbacks(any(Runnable.class));
+        verify(mHandler).removeCallbacksAndMessages(null);
     }
 
     @Test
@@ -281,4 +281,16 @@
 
         verify(mAnimationsController).cancelAnimations();
     }
+
+    @Test
+    public void onViewAttached_addsScrimExpansionCallback() {
+        mController.onViewAttached();
+        verify(mBouncerlessScrimController).addCallback(any());
+    }
+
+    @Test
+    public void onViewDetached_removesScrimExpansionCallback() {
+        mController.onViewDetached();
+        verify(mBouncerlessScrimController).removeCallback(any());
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 3b6f6a2..f31eb7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -58,7 +58,7 @@
 @ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -88,7 +88,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index f52c66e..cde703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -43,7 +43,7 @@
 @ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val repository = kosmos.fakeKeyguardTransitionRepository
@@ -60,7 +60,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index fee18dd..d632936 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -16,13 +16,15 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,29 +33,25 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class BouncerToGoneFlowsTest : SysuiTestCase() {
-    @Mock private lateinit var shadeInteractor: ShadeInteractor
-
-    private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+@RunWith(ParameterizedAndroidJunit4::class)
+class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos =
         testKosmos().apply {
@@ -61,16 +59,31 @@
         }
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
     private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
     private val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
-    private val underTest = kosmos.bouncerToGoneFlows
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: BouncerToGoneFlows
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
         sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
+        underTest = kosmos.bouncerToGoneFlows
     }
 
     @Test
@@ -79,7 +92,7 @@
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
             runCurrent()
 
-            shadeRepository.setLockscreenShadeExpansion(1f)
+            shadeTestUtil.setLockscreenShadeExpansion(1f)
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -99,12 +112,13 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
             runCurrent()
 
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
 
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 2e1765a..838b2a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -60,7 +60,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
@@ -75,7 +75,6 @@
 
     private val viewState = ViewStateAccessor()
 
-    // add to init block
     companion object {
         @JvmStatic
         @Parameters(name = "{0}")
@@ -85,7 +84,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index ec2cb04..de4b999 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -47,7 +47,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos: Kosmos = testKosmos()
 
@@ -62,7 +62,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index e3ae3ba..bc381f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -49,7 +49,7 @@
 @ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -73,7 +73,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index adeb395..9337793 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -51,7 +51,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos =
         testKosmos().apply {
@@ -73,7 +73,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index f8da74f..6ce7e88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -54,7 +54,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -76,7 +76,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index d5df159..58c6817 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -46,7 +46,7 @@
 @ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) :
+class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) :
     SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
@@ -67,7 +67,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
new file mode 100644
index 0000000..a0aa2d4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.DateFormatUtil
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testUser = UserHandle.of(1)!!
+    private val testStartTime = LocalTime.MIDNIGHT
+    private val testEndTime = LocalTime.NOON
+    private val colorDisplayManager =
+        mock<ColorDisplayManager> {
+            whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+            whenever(isNightDisplayActivated).thenReturn(false)
+            whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+            whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+        }
+    private val locationController = FakeLocationController(LeakCheck())
+    private val nightDisplayListener = mock<NightDisplayListener>()
+    private val listenerBuilder =
+        mock<NightDisplayListenerModule.Builder> {
+            whenever(setUser(anyInt())).thenReturn(this)
+            whenever(build()).thenReturn(nightDisplayListener)
+        }
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val secureSettings = kosmos.fakeSettings
+    private val dateFormatUtil = mock<DateFormatUtil> { whenever(is24HourFormat).thenReturn(false) }
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val userScopedColorDisplayManager =
+        mock<UserScopedService<ColorDisplayManager>> {
+            whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+        }
+    private val nightDisplayRepository =
+        NightDisplayRepository(
+            testDispatcher,
+            scope.backgroundScope,
+            globalSettings,
+            secureSettings,
+            listenerBuilder,
+            userScopedColorDisplayManager,
+            locationController,
+        )
+
+    private val underTest: NightDisplayTileDataInteractor =
+        NightDisplayTileDataInteractor(context, dateFormatUtil, nightDisplayRepository)
+
+    @Test
+    fun availability_matchesColorDisplayManager() = runTest {
+        val availability by collectLastValue(underTest.availability(testUser))
+
+        val expectedAvailability = ColorDisplayManager.isNightDisplayAvailable(context)
+        assertThat(availability).isEqualTo(expectedAvailability)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..adc8bcb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val testUser = UserHandle.of(1)
+    private val colorDisplayManager =
+        mock<ColorDisplayManager> {
+            whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+            whenever(isNightDisplayActivated).thenReturn(false)
+        }
+    private val locationController = FakeLocationController(LeakCheck())
+    private val nightDisplayListener = mock<NightDisplayListener>()
+    private val listenerBuilder =
+        mock<NightDisplayListenerModule.Builder> {
+            whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+            whenever(build()).thenReturn(nightDisplayListener)
+        }
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val secureSettings = kosmos.fakeSettings
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val userScopedColorDisplayManager =
+        mock<UserScopedService<ColorDisplayManager>> {
+            whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+        }
+    private val nightDisplayRepository =
+        NightDisplayRepository(
+            testDispatcher,
+            scope.backgroundScope,
+            globalSettings,
+            secureSettings,
+            listenerBuilder,
+            userScopedColorDisplayManager,
+            locationController,
+        )
+
+    private val underTest =
+        NightDisplayTileUserActionInteractor(
+            nightDisplayRepository,
+            qsTileIntentUserActionHandler,
+            kosmos.qsTileLogger
+        )
+
+    @Test
+    fun handleClick_inactive_activates() =
+        scope.runTest {
+            val startingModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(true)
+        }
+
+    @Test
+    fun handleClick_active_disables() =
+        scope.runTest {
+            val startingModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(false)
+        }
+
+    @Test
+    fun handleClick_whenAutoModeTwilight_flipsState() =
+        scope.runTest {
+            val originalState = true
+            val startingModel = NightDisplayTileModel.AutoModeTwilight(originalState, false, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+        }
+
+    @Test
+    fun handleClick_whenAutoModeCustom_flipsState() =
+        scope.runTest {
+            val originalState = true
+            val startingModel =
+                NightDisplayTileModel.AutoModeCustom(originalState, false, null, null, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+        }
+
+    @Test
+    fun handleLongClickWhenEnabled() =
+        scope.runTest {
+            val enabledState = true
+
+            underTest.handleInput(
+                QSTileInputTestKtx.longClick(
+                    NightDisplayTileModel.AutoModeOff(enabledState, false),
+                    testUser
+                )
+            )
+
+            assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+            val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+            val actualIntentAction = intentInput.intent.action
+            val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+            assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+        }
+
+    @Test
+    fun handleLongClickWhenDisabled() =
+        scope.runTest {
+            val enabledState = false
+
+            underTest.handleInput(
+                QSTileInputTestKtx.longClick(
+                    NightDisplayTileModel.AutoModeOff(enabledState, false),
+                    testUser
+                )
+            )
+
+            assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+            val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+            val actualIntentAction = intentInput.intent.action
+            val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+            assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
new file mode 100644
index 0000000..5d2e701
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.mock
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsNightDisplayTileConfig
+
+    private val testStartTime = LocalTime.MIDNIGHT
+    private val testEndTime = LocalTime.NOON
+
+    private lateinit var mapper: NightDisplayTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            NightDisplayTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable())
+                        addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable())
+                    }
+                    .resources,
+                context.theme,
+                mock<QSTileLogger>(),
+            )
+    }
+
+    @Test
+    fun disabledModel_whenAutoModeOff() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    /** Force enable does not change the mode by itself. */
+    @Test
+    fun disabledModel_whenAutoModeOff_whenForceEnable() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(false, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_whenAutoModeOff() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_forceAutoMode_whenAutoModeOff() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(true, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeTwilight_locationOff() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeTwilight_locationOn() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeTwilight_locationOn() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeTwilight_locationOff() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeCustom_24Hour() {
+        val inputModel =
+            NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(
+                    R.string.quick_settings_night_secondary_label_on_at,
+                    formatter24Hour.format(testStartTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeCustom_12Hour() {
+        val inputModel =
+            NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(
+                    R.string.quick_settings_night_secondary_label_on_at,
+                    formatter12Hour.format(testStartTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */
+    @Test
+    fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() {
+        val inputModel =
+            NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(
+                    R.string.quick_settings_night_secondary_label_on_at,
+                    formatter12Hour.format(testStartTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeCustom_24Hour() {
+        val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(
+                    R.string.quick_settings_secondary_label_until,
+                    formatter24Hour.format(testEndTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeCustom_12Hour() {
+        val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(
+                    R.string.quick_settings_secondary_label_until,
+                    formatter12Hour.format(testEndTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */
+    @Test
+    fun enabledModel_autoModeCustom_24Hour_forceEnabled() {
+        val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(
+                    R.string.quick_settings_secondary_label_until,
+                    formatter24Hour.format(testEndTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createNightDisplayTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String?
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_night_display_label)
+
+        val contentDescription =
+            if (TextUtils.isEmpty(secondaryLabel)) label
+            else TextUtils.concat(label, ", ", secondaryLabel)
+        return QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(
+                        if (activationState == QSTileState.ActivationState.ACTIVE)
+                            R.drawable.qs_nightlight_icon_on
+                        else R.drawable.qs_nightlight_icon_off
+                    )!!,
+                    null
+                )
+            },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            contentDescription,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+
+    private companion object {
+        val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+        val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index aa0ca18..78c4def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -60,7 +60,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -85,7 +85,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 44c9695..cecc70c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -60,7 +60,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val shadeInteractor by lazy { kosmos.shadeInteractor }
@@ -80,7 +80,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d353a62..f06e04b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,10 @@
 import com.android.systemui.flags.parameterizeSceneContainerFlag
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -65,11 +68,11 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @TestableLooper.RunWithLooper
-class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val mockDarkAnimator = mock<ObjectAnimator>()
 
     private lateinit var underTest: StatusBarStateControllerImpl
@@ -84,7 +87,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
@@ -98,6 +101,7 @@
                     uiEventLogger,
                     { kosmos.interactionJankMonitor },
                     JavaAdapter(testScope.backgroundScope),
+                    { kosmos.keyguardTransitionInteractor },
                     { kosmos.shadeInteractor },
                     { kosmos.deviceUnlockedInteractor },
                     { kosmos.sceneInteractor },
@@ -330,4 +334,25 @@
             assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
         }
+
+    @Test
+    fun leaveOpenOnKeyguard_whenGone_isFalse() =
+        testScope.runTest {
+            underTest.start()
+            underTest.setLeaveOpenOnKeyguardHide(true)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+            assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+            assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index cbbc4d8..9367a93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -59,7 +59,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) :
+class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) :
     SysuiTestCase() {
 
     companion object {
@@ -71,7 +71,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 7ac549a..cc5df74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -20,12 +20,13 @@
 
 import android.app.NotificationManager.Policy
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -56,11 +57,13 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
-class NotificationListViewModelTest : SysuiTestCase() {
+class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -72,16 +75,30 @@
     private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
     private val fakePowerRepository = kosmos.fakePowerRepository
     private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
-    private val fakeShadeRepository = kosmos.fakeShadeRepository
     private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
     private val headsUpRepository = kosmos.headsUpNotificationRepository
     private val zenModeRepository = kosmos.zenModeRepository
 
-    val underTest = kosmos.notificationListViewModel
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: NotificationListViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        underTest = kosmos.notificationListViewModel
     }
 
     @Test
@@ -163,7 +180,7 @@
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            fakeShadeRepository.legacyQsFullscreen.value = true
+            shadeTestUtil.setQsFullscreen(true)
             runCurrent()
 
             // THEN empty shade is not visible
@@ -178,9 +195,10 @@
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            fakeShadeRepository.setQsExpansion(1f)
-            // AND split shade is enabled
+            shadeTestUtil.setQsExpansion(1f)
+            // AND split shade is expanded
             overrideResource(R.bool.config_use_split_notification_shade, true)
+            shadeTestUtil.setShadeExpansion(1f)
             fakeConfigurationController.notifyConfigurationChanged()
             runCurrent()
 
@@ -290,7 +308,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer is visible
@@ -306,7 +324,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open on lockscreen
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer is visible
@@ -337,7 +355,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND user is not set up
             fakeUserSetupRepository.setUserSetUp(false)
             runCurrent()
@@ -355,7 +373,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND device is starting to go to sleep
             fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
             runCurrent()
@@ -373,10 +391,10 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND quick settings are expanded
-            fakeShadeRepository.setQsExpansion(1f)
-            fakeShadeRepository.legacyQsFullscreen.value = true
+            shadeTestUtil.setQsExpansion(1f)
+            shadeTestUtil.setQsFullscreen(true)
             runCurrent()
 
             // THEN footer is not visible
@@ -390,11 +408,11 @@
 
             // WHEN has notifs
             activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND quick settings are expanded
+            shadeTestUtil.setQsExpansion(1f)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
-            // AND quick settings are expanded
-            fakeShadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND split shade is enabled
             overrideResource(R.bool.config_use_split_notification_shade, true)
             fakeConfigurationController.notifyConfigurationChanged()
@@ -413,7 +431,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND remote input is active
             fakeRemoteInputRepository.isRemoteInputActive.value = true
             runCurrent()
@@ -431,7 +449,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open and fully expanded
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer visibility animates
@@ -447,7 +465,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND we are on the keyguard
             fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer visibility does not animate
@@ -461,7 +479,7 @@
 
             // WHEN shade is closed
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             runCurrent()
 
             // THEN footer is hidden
@@ -475,7 +493,7 @@
 
             // WHEN shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer is hidden
@@ -489,8 +507,8 @@
 
             // WHEN QS partially open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setQsExpansion(0.5f)
-            fakeShadeRepository.setLegacyShadeExpansion(0.5f)
+            shadeTestUtil.setQsExpansion(0.5f)
+            shadeTestUtil.setShadeExpansion(0.5f)
             runCurrent()
 
             // THEN footer is hidden
@@ -588,7 +606,7 @@
         testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
 
-            fakeShadeRepository.setQsExpansion(0.0f)
+            shadeTestUtil.setQsExpansion(0.0f)
             fakeKeyguardRepository.setKeyguardShowing(false)
             runCurrent()
 
@@ -601,7 +619,7 @@
         testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
 
-            fakeShadeRepository.setQsExpansion(0.0f)
+            shadeTestUtil.setQsExpansion(0.0f)
             fakeKeyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 2cd295c..f2ce745 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -75,7 +75,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 // SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on
 @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     companion object {
         @JvmStatic
@@ -89,7 +89,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8ce5037..63f19fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -249,22 +249,40 @@
 
 
     @Test
-    fun testDelete_showingEntryKeyBecomesPreviousHunKey() {
+    fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() {
         mAvalancheController.previousHunKey = ""
 
         // Entry is showing
+        val firstEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = firstEntry
+
+        // There's another entry waiting to show next
+        val secondEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(secondEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
+
+        // Next entry is shown
+        assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
+    }
+
+    @Test
+    fun testDelete_deleteLastEntry_previousHunKeyCleared() {
+        mAvalancheController.previousHunKey = "key"
+
+        // Nothing waiting to show
+        mAvalancheController.clearNext()
+
+        // One entry is showing
         val showingEntry = createHeadsUpEntry(id = 0)
         mAvalancheController.headsUpEntryShowing = showingEntry
 
-        // There's another entry waiting to show next
-        val nextEntry = createHeadsUpEntry(id = 1)
-        mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
         // Delete
-        mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+        mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
 
         // Next entry is shown
-        assertThat(mAvalancheController.previousHunKey).isEqualTo(showingEntry.mEntry!!.key)
+        assertThat(mAvalancheController.previousHunKey).isEqualTo("");
     }
 
     @Test
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1daebd..5857692 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -285,6 +285,9 @@
     the amount by the view is positioned above the screen before the animation starts. -->
     <dimen name="heads_up_appear_y_above_screen">32dp</dimen>
 
+    <!-- padding between the old and new heads up notifications for the hun cycling animation -->
+    <dimen name="heads_up_cycling_padding">8dp</dimen>
+
     <!-- padding between the heads up and the statusbar -->
     <dimen name="heads_up_status_bar_padding">8dp</dimen>
 
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 422f02f..8979ef1 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.biometrics
 
 import android.Manifest
-import android.annotation.IntDef
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
@@ -39,14 +38,9 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityManager
 import com.android.internal.widget.LockPatternUtils
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
+import com.android.systemui.biometrics.shared.model.PromptKind
 
 object Utils {
-    const val CREDENTIAL_PIN = 1
-    const val CREDENTIAL_PATTERN = 2
-    const val CREDENTIAL_PASSWORD = 3
-
     /** Base set of layout flags for fingerprint overlay widgets. */
     const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
         (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
@@ -91,17 +85,16 @@
         (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
 
     @JvmStatic
-    @CredentialType
-    fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+    fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind =
         when (utils.getKeyguardStoredPasswordQuality(userId)) {
-            PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+            PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern
             PASSWORD_QUALITY_NUMERIC,
-            PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+            PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin
             PASSWORD_QUALITY_ALPHABETIC,
             PASSWORD_QUALITY_ALPHANUMERIC,
             PASSWORD_QUALITY_COMPLEX,
-            PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
-            else -> CREDENTIAL_PASSWORD
+            PASSWORD_QUALITY_MANAGED -> PromptKind.Password
+            else -> PromptKind.Password
         }
 
     @JvmStatic
@@ -129,8 +122,4 @@
         return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars())
             ?: Insets.NONE
     }
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
-    annotation class CredentialType
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index a97e2dc..8782962 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -16,16 +16,20 @@
 
 package com.android.systemui.biometrics.shared.model
 
-import com.android.systemui.biometrics.Utils
-
-// TODO(b/251476085): this should eventually replace Utils.CredentialType
-/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
 sealed interface PromptKind {
+    object None : PromptKind
+
     data class Biometric(
         val activeModalities: BiometricModalities = BiometricModalities(),
+        // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of
+        // simply depending on rotations.
+        val showTwoPane: Boolean = false
     ) : PromptKind
 
     object Pin : PromptKind
     object Pattern : PromptKind
     object Password : PromptKind
+
+    fun isBiometric() = this is Biometric
+    fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password)
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 460779c..f33acf2 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -432,6 +433,7 @@
                         listenForDozeAmountTransition(this)
                         listenForAnyStateToAodTransition(this)
                         listenForAnyStateToLockscreenTransition(this)
+                        listenForAnyStateToDozingTransition(this)
                     } else {
                         listenForDozeAmount(this)
                     }
@@ -578,6 +580,21 @@
         }
     }
 
+    /**
+     * When keyguard is displayed due to pulsing notifications when AOD is off,
+     * we should make sure clock is in dozing state instead of LS state
+     */
+    @VisibleForTesting
+    internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job {
+        return scope.launch {
+            keyguardTransitionInteractor
+                    .transitionStepsToState(DOZING)
+                    .filter { it.transitionState == TransitionState.FINISHED }
+                    .collect { handleDoze(1f) }
+        }
+    }
+
+
     @VisibleForTesting
     internal fun listenForDozing(scope: CoroutineScope): Job {
         return scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
new file mode 100644
index 0000000..8f071e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.accessibility.data.model
+
+import java.time.LocalTime
+
+sealed interface NightDisplayChangeEvent {
+    data class OnAutoModeChanged(val autoMode: Int) : NightDisplayChangeEvent
+    data class OnActivatedChanged(val isActivated: Boolean) : NightDisplayChangeEvent
+    data class OnCustomStartTimeChanged(val startTime: LocalTime?) : NightDisplayChangeEvent
+    data class OnCustomEndTimeChanged(val endTime: LocalTime?) : NightDisplayChangeEvent
+    data class OnForceAutoModeChanged(val shouldForceAutoMode: Boolean) : NightDisplayChangeEvent
+    data class OnLocationEnabledChanged(val locationEnabled: Boolean) : NightDisplayChangeEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
new file mode 100644
index 0000000..196876e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.model
+
+import java.time.LocalTime
+
+/** models the state of NightDisplayRepository */
+data class NightDisplayState(
+    val autoMode: Int = 0,
+    val isActivated: Boolean = true,
+    val startTime: LocalTime? = null,
+    val endTime: LocalTime? = null,
+    val shouldForceAutoMode: Boolean = false,
+    val locationEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
index ae9f57f..6032f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
@@ -18,7 +18,7 @@
 
 import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.channels.awaitClose
@@ -29,6 +29,8 @@
 interface AccessibilityRepository {
     /** @see [AccessibilityManager.isTouchExplorationEnabled] */
     val isTouchExplorationEnabled: Flow<Boolean>
+    /** @see [AccessibilityManager.isEnabled] */
+    val isEnabled: Flow<Boolean>
 
     companion object {
         operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository =
@@ -47,6 +49,15 @@
                 awaitClose { manager.removeTouchExplorationStateChangeListener(listener) }
             }
             .distinctUntilChanged()
+
+    override val isEnabled: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend)
+                manager.addAccessibilityStateChangeListener(listener)
+                trySend(manager.isEnabled)
+                awaitClose { manager.removeAccessibilityStateChangeListener(listener) }
+            }
+            .distinctUntilChanged()
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
new file mode 100644
index 0000000..bf44fab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent
+import com.android.systemui.accessibility.data.model.NightDisplayState
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+class NightDisplayRepository
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val globalSettings: GlobalSettings,
+    private val secureSettings: SecureSettings,
+    private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+    private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>,
+    private val locationController: LocationController,
+) {
+    private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>()
+
+    fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> =
+        stateFlowUserMap.getOrPut(user.identifier) {
+            return merge(
+                    colorDisplayManagerChangeEventFlow(user),
+                    shouldForceAutoMode(user).map {
+                        NightDisplayChangeEvent.OnForceAutoModeChanged(it)
+                    },
+                    locationController.isLocationEnabledFlow().map {
+                        NightDisplayChangeEvent.OnLocationEnabledChanged(it)
+                    }
+                )
+                .scan(initialState(user)) { state, event ->
+                    when (event) {
+                        is NightDisplayChangeEvent.OnActivatedChanged ->
+                            state.copy(isActivated = event.isActivated)
+                        is NightDisplayChangeEvent.OnAutoModeChanged ->
+                            state.copy(autoMode = event.autoMode)
+                        is NightDisplayChangeEvent.OnCustomStartTimeChanged ->
+                            state.copy(startTime = event.startTime)
+                        is NightDisplayChangeEvent.OnCustomEndTimeChanged ->
+                            state.copy(endTime = event.endTime)
+                        is NightDisplayChangeEvent.OnForceAutoModeChanged ->
+                            state.copy(shouldForceAutoMode = event.shouldForceAutoMode)
+                        is NightDisplayChangeEvent.OnLocationEnabledChanged ->
+                            state.copy(locationEnabled = event.locationEnabled)
+                    }
+                }
+                .conflate()
+                .onStart { emit(initialState(user)) }
+                .flowOn(bgCoroutineContext)
+                .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState())
+        }
+
+    /** Track changes in night display enabled state and its auto mode */
+    private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow {
+        val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build()
+        val nightDisplayCallback =
+            object : NightDisplayListener.Callback {
+                override fun onActivated(activated: Boolean) {
+                    trySend(NightDisplayChangeEvent.OnActivatedChanged(activated))
+                }
+
+                override fun onAutoModeChanged(autoMode: Int) {
+                    trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode))
+                }
+
+                override fun onCustomStartTimeChanged(startTime: LocalTime?) {
+                    trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime))
+                }
+
+                override fun onCustomEndTimeChanged(endTime: LocalTime?) {
+                    trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime))
+                }
+            }
+        nightDisplayListener.setCallback(nightDisplayCallback)
+        awaitClose { nightDisplayListener.setCallback(null) }
+    }
+
+    /** @return true when the option to force auto mode is available and a value has not been set */
+    private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> =
+        combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) {
+            isForceAutoModeAvailable,
+            isDisplayAutoModeRawNotSet,
+            ->
+            isForceAutoModeAvailable && isDisplayAutoModeRawNotSet
+        }
+
+    private val isForceAutoModeAvailable: Flow<Boolean> =
+        globalSettings
+            .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME)
+            .onStart { emit(Unit) }
+            .map {
+                globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+                    NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+            }
+            .distinctUntilChanged()
+
+    /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */
+    private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> =
+        if (userHandle.identifier == UserHandle.USER_NULL) {
+                flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT)
+            } else {
+                secureSettings
+                    .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME)
+                    .onStart { emit(Unit) }
+                    .map {
+                        secureSettings.getIntForUser(
+                            DISPLAY_AUTO_MODE_RAW_SETTING_NAME,
+                            userHandle.identifier
+                        ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET
+                    }
+            }
+            .distinctUntilChanged()
+
+    suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) {
+        withContext(bgCoroutineContext) {
+            colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode
+        }
+    }
+
+    suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) {
+        withContext(bgCoroutineContext) {
+            colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated
+        }
+    }
+
+    private fun initialState(user: UserHandle): NightDisplayState {
+        val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user)
+        return NightDisplayState(
+            colorDisplayManager.nightDisplayAutoMode,
+            colorDisplayManager.isNightDisplayActivated,
+            colorDisplayManager.nightDisplayCustomStartTime,
+            colorDisplayManager.nightDisplayCustomEndTime,
+            globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+                NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE &&
+                secureSettings.getIntForUser(DISPLAY_AUTO_MODE_RAW_SETTING_NAME, user.identifier) ==
+                    NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET,
+            locationController.isLocationEnabled,
+        )
+    }
+
+    private companion object {
+        const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+        const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+        const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true
+        const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME =
+            Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+        const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
index 968ce0d..93b624a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
@@ -28,6 +28,8 @@
     private val a11yRepo: AccessibilityRepository,
 ) {
     /** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */
-    val isTouchExplorationEnabled: Flow<Boolean>
-        get() = a11yRepo.isTouchExplorationEnabled
+    val isTouchExplorationEnabled: Flow<Boolean> = a11yRepo.isTouchExplorationEnabled
+
+    /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */
+    val isEnabled: Flow<Boolean> = a11yRepo.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 54dd6d0..ed9597d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -41,6 +41,10 @@
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.ui.NightDisplayTileMapper
 import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
@@ -117,6 +121,7 @@
         const val FONT_SCALING_TILE_SPEC = "font_scaling"
         const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
         const val ONE_HANDED_TILE_SPEC = "onehanded"
+        const val NIGHT_DISPLAY_TILE_SPEC = "night"
 
         @Provides
         @IntoMap
@@ -279,5 +284,41 @@
                     mapper,
                 )
             else StubQSTileViewModel
+
+        @Provides
+        @IntoMap
+        @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+        fun provideNightDisplayTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_nightlight_icon_off,
+                        labelRes = R.string.quick_settings_night_display_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /**
+         * Inject NightDisplay Tile into tileViewModelMap in QSModule. The tile is hidden behind a
+         * flag.
+         */
+        @Provides
+        @IntoMap
+        @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+        fun provideNightDisplayTileViewModel(
+            factory: QSTileViewModelFactory.Static<NightDisplayTileModel>,
+            mapper: NightDisplayTileMapper,
+            stateInteractor: NightDisplayTileDataInteractor,
+            userActionInteractor: NightDisplayTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 5ba0b2d..9ba41ef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -76,6 +76,7 @@
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
 import com.android.systemui.biometrics.shared.model.BiometricModalities;
+import com.android.systemui.biometrics.shared.model.PromptKind;
 import com.android.systemui.biometrics.ui.BiometricPromptLayout;
 import com.android.systemui.biometrics.ui.CredentialView;
 import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
@@ -500,24 +501,18 @@
     private void addCredentialView(boolean animatePanel, boolean animateContents) {
         final LayoutInflater factory = LayoutInflater.from(mContext);
 
-        @Utils.CredentialType final int credentialType = Utils.getCredentialType(
-                mLockPatternUtils, mEffectiveUserId);
-
-        switch (credentialType) {
-            case Utils.CREDENTIAL_PATTERN:
-                mCredentialView = factory.inflate(
-                        R.layout.auth_credential_pattern_view, null, false);
-                break;
-            case Utils.CREDENTIAL_PIN:
-                mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false);
-                break;
-            case Utils.CREDENTIAL_PASSWORD:
-                mCredentialView = factory.inflate(
-                        R.layout.auth_credential_password_view, null, false);
-                break;
-            default:
-                throw new IllegalStateException("Unknown credential type: " + credentialType);
+        PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
+        final int layoutResourceId;
+        if (credentialType instanceof PromptKind.Pattern) {
+            layoutResourceId = R.layout.auth_credential_pattern_view;
+        } else if (credentialType instanceof PromptKind.Pin) {
+            layoutResourceId = R.layout.auth_credential_pin_view;
+        } else if (credentialType instanceof PromptKind.Password) {
+            layoutResourceId = R.layout.auth_credential_password_view;
+        } else {
+            throw new IllegalStateException("Unknown credential type: " + credentialType);
         }
+        mCredentialView = factory.inflate(layoutResourceId, null, false);
 
         // The background is used for detecting taps / cancelling authentication. Since the
         // credential view is full-screen and should not be canceled from background taps,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index 9ad3f43..f659ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -125,7 +125,7 @@
     private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
     override val userId = _userId.asStateFlow()
 
-    private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
+    private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None)
     override val kind = _kind.asStateFlow()
 
     private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
@@ -149,7 +149,7 @@
     override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
 
     override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
-        val hasCredentialViewShown = kind.value !is PromptKind.Biometric
+        val hasCredentialViewShown = kind.value.isCredential()
         val showBpForCredential =
             Flags.customBiometricPrompt() &&
                 constraintBp() &&
@@ -178,7 +178,7 @@
         _promptInfo.value = null
         _userId.value = null
         _challenge.value = null
-        _kind.value = PromptKind.Biometric()
+        _kind.value = PromptKind.None
         _opPackageName.value = null
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index b7c0fa8..f8fb7bb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.biometrics.domain.interactor
 
-import android.hardware.biometrics.PromptInfo
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
@@ -42,12 +40,6 @@
  * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
  * PIN, pattern, or password credential instead of a biometric.
  *
- * This is used to cache the calling app's options that were given to the underlying authenticate
- * APIs and should be set before any UI is shown to the user.
- *
- * There can be at most one request active at a given time. Use [resetPrompt] when no request is
- * active to clear the cache.
- *
  * Views that use any biometric should use [PromptSelectorInteractor] instead.
  */
 class PromptCredentialInteractor
@@ -137,28 +129,6 @@
     private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
     val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
 
-    /** Update the current request to use credential-based authentication instead of biometrics. */
-    fun useCredentialsForAuthentication(
-        promptInfo: PromptInfo,
-        @Utils.CredentialType kind: Int,
-        userId: Int,
-        challenge: Long,
-        opPackageName: String,
-    ) {
-        biometricPromptRepository.setPrompt(
-            promptInfo,
-            userId,
-            challenge,
-            kind.asBiometricPromptCredential(),
-            opPackageName,
-        )
-    }
-
-    /** Unset the current authentication request. */
-    fun resetPrompt() {
-        biometricPromptRepository.unsetPrompt()
-    }
-
     /**
      * Check a credential and return the attestation token (HAT) if successful.
      *
@@ -231,13 +201,3 @@
         _verificationError.value = null
     }
 }
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
-    when (this) {
-        Utils.CREDENTIAL_PIN -> PromptKind.Pin
-        Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
-        Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
-        else -> PromptKind.Biometric()
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 45816c1..deb47d3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -18,7 +18,6 @@
 
 import android.hardware.biometrics.PromptInfo
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.Utils.getCredentialType
 import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
@@ -95,7 +94,7 @@
     /** Use credential-based authentication instead of biometrics. */
     fun useCredentialsForAuthentication(
         promptInfo: PromptInfo,
-        @Utils.CredentialType kind: Int,
+        kind: PromptKind,
         userId: Int,
         challenge: Long,
         opPackageName: String,
@@ -152,14 +151,7 @@
     override val credentialKind: Flow<PromptKind> =
         combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
             if (prompt != null && isAllowed) {
-                when (
-                    getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
-                ) {
-                    Utils.CREDENTIAL_PIN -> PromptKind.Pin
-                    Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
-                    Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
-                    else -> PromptKind.Biometric()
-                }
+                getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
             } else {
                 PromptKind.Biometric()
             }
@@ -191,7 +183,7 @@
 
     override fun useCredentialsForAuthentication(
         promptInfo: PromptInfo,
-        @Utils.CredentialType kind: Int,
+        kind: PromptKind,
         userId: Int,
         challenge: Long,
         opPackageName: String,
@@ -200,7 +192,7 @@
             promptInfo = promptInfo,
             userId = userId,
             gatekeeperChallenge = challenge,
-            kind = kind.asBiometricPromptCredential(),
+            kind = kind,
             opPackageName = opPackageName,
         )
     }
@@ -209,13 +201,3 @@
         promptRepository.unsetPrompt()
     }
 }
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
-    when (this) {
-        Utils.CREDENTIAL_PIN -> PromptKind.Pin
-        Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
-        Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
-        else -> PromptKind.Biometric()
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 337d873..9114aab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -33,6 +33,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
 /** View model for transitions related to the communal hub. */
@@ -49,6 +50,27 @@
     communalInteractor: CommunalInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
+    // Show UMO on glanceable hub immediately on transition into glanceable hub
+    private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionStepsFromState(KeyguardState.OCCLUDED)
+            .filter {
+                it.to == KeyguardState.GLANCEABLE_HUB &&
+                    (it.transitionState == TransitionState.STARTED ||
+                        it.transitionState == TransitionState.CANCELED)
+            }
+            .map { it.transitionState == TransitionState.STARTED }
+
+    private val showUmoFromGlanceableHubToOccluded: Flow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionStepsFromState(KeyguardState.GLANCEABLE_HUB)
+            .filter {
+                it.to == KeyguardState.OCCLUDED &&
+                    (it.transitionState == TransitionState.FINISHED ||
+                        it.transitionState == TransitionState.CANCELED)
+            }
+            .map { it.transitionState != TransitionState.FINISHED }
+
     /**
      * Whether UMO location should be on communal. This flow is responsive to transitions so that a
      * new value is emitted at the right step of a transition to/from communal hub that the location
@@ -60,6 +82,8 @@
                 glanceableHubToLockscreenTransitionViewModel.showUmo,
                 dreamToGlanceableHubTransitionViewModel.showUmo,
                 glanceableHubToDreamTransitionViewModel.showUmo,
+                showUmoFromOccludedToGlanceableHub,
+                showUmoFromGlanceableHubToOccluded,
             )
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index e00137e..11e6f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -225,6 +225,13 @@
 
     @Provides
     @Singleton
+    static UserScopedService<ColorDisplayManager> provideScopedColorDisplayManager(
+            Context context) {
+        return new UserScopedServiceImpl<>(context, ColorDisplayManager.class);
+    }
+
+    @Provides
+    @Singleton
     static CrossWindowBlurListeners provideCrossWindowBlurListeners() {
         return CrossWindowBlurListeners.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 8c0a73c..6e04339 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -198,7 +198,6 @@
         mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
 
         mBouncerlessScrimController = bouncerlessScrimController;
-        mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
 
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
 
@@ -234,6 +233,7 @@
         mJitterStartTimeMillis = System.currentTimeMillis();
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
         mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+        mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
         final Region emptyRegion = Region.obtain();
         mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
         emptyRegion.recycle();
@@ -255,8 +255,9 @@
 
     @Override
     protected void onViewDetached() {
-        mHandler.removeCallbacks(this::updateBurnInOffsets);
+        mHandler.removeCallbacksAndMessages(null);
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+        mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
 
         mDreamOverlayAnimationsController.cancelAnimations();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e32bfcf..7f3274c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -134,7 +134,7 @@
             TransitionInfo(
                 ownerName = "",
                 from = KeyguardState.OFF,
-                to = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OFF,
                 animator = null
             )
         )
@@ -266,6 +266,14 @@
     }
 
     override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+        _currentTransitionInfo.value =
+            TransitionInfo(
+                ownerName = "KeyguardTransitionRepository(boot)",
+                from = KeyguardState.OFF,
+                to = to,
+                animator = null
+            )
+
         emitTransition(
             TransitionStep(
                 KeyguardState.OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 4f00495..e2b66c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -76,7 +76,7 @@
                         view,
                         HapticFeedbackConstants.CONFIRM,
                     )
-                    applicationScope.launch { viewModel.onLongPress() }
+                    applicationScope.launch { viewModel.onUserInteraction() }
                 }
             }
 
@@ -116,6 +116,17 @@
                 launch("$TAG#viewModel.accessibilityDelegateHint") {
                     viewModel.accessibilityDelegateHint.collect { hint ->
                         view.accessibilityHintType = hint
+                        if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+                            view.setOnClickListener {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    HapticFeedbackConstants.CONFIRM,
+                                )
+                                applicationScope.launch { viewModel.onUserInteraction() }
+                            }
+                        } else {
+                            view.setOnClickListener(null)
+                        }
                     }
                 }
                 launch("$TAG#viewModel.useBackgroundProtection") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 35b2598..200d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -65,12 +65,12 @@
             object : AccessibilityDelegate() {
                 private val accessibilityAuthenticateHint =
                     AccessibilityNodeInfo.AccessibilityAction(
-                        AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
                         resources.getString(R.string.accessibility_authenticate_hint)
                     )
                 private val accessibilityEnterHint =
                     AccessibilityNodeInfo.AccessibilityAction(
-                        AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
                         resources.getString(R.string.accessibility_enter_hint)
                     )
                 override fun onInitializeAccessibilityNodeInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index da2fcc4..53b2697 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -19,6 +19,7 @@
 import android.animation.FloatEvaluator
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -68,6 +69,7 @@
     private val keyguardViewController: Lazy<KeyguardViewController>,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+    private val accessibilityInteractor: AccessibilityInteractor,
     @Application private val scope: CoroutineScope,
 ) {
     val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
@@ -232,7 +234,8 @@
             }
         }
     val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
-    val isLongPressEnabled: Flow<Boolean> =
+
+    private val isInteractive: Flow<Boolean> =
         combine(
             iconType,
             isUdfpsSupported,
@@ -244,17 +247,24 @@
                 DeviceEntryIconView.IconType.NONE -> false
             }
         }
-
     val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
-        combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
-            if (longPressEnabled) {
-                deviceEntryStatus.toAccessibilityHintType()
+        accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+            if (touchExplorationEnabled) {
+                combine(iconType, isInteractive) { iconType, isInteractive ->
+                    if (isInteractive) {
+                        iconType.toAccessibilityHintType()
+                    } else {
+                        DeviceEntryIconView.AccessibilityHintType.NONE
+                    }
+                }
             } else {
-                DeviceEntryIconView.AccessibilityHintType.NONE
+                flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
             }
         }
 
-    suspend fun onLongPress() {
+    val isLongPressEnabled: Flow<Boolean> = isInteractive
+
+    suspend fun onUserInteraction() {
         if (SceneContainerFlag.isEnabled) {
             deviceEntryInteractor.attemptDeviceEntry()
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 933065b..295b293 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -442,7 +442,7 @@
                                         | PackageManager.MATCH_DISABLED_COMPONENTS
                                         | PackageManager.GET_SHARED_LIBRARY_FILES));
                 int resId = resources.getIdentifier(
-                        "gesture_blocking_activities", "array", recentsPackageName);
+                        "back_gesture_blocking_activities", "array", recentsPackageName);
 
                 if (resId == 0) {
                     Log.e(TAG, "No resource found for gesture-blocking activities");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 6fb5174..5720f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -125,7 +125,10 @@
 
     public void setNumPages(int numPages) {
         setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
-        if (numPages == getChildCount()) {
+        int childCount = getChildCount();
+        // We're checking if the width needs to be updated as it's possible that the number of pages
+        // was changed while the page indicator was not visible, automatically skipping onMeasure.
+        if (numPages == childCount && calculateWidth(childCount) == getMeasuredWidth()) {
             return;
         }
         if (mAnimating) {
@@ -295,6 +298,10 @@
         }
     }
 
+    private int calculateWidth(int numPages) {
+        return (mPageIndicatorWidth - mPageDotWidth) * (numPages - 1) + mPageDotWidth;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int N = getChildCount();
@@ -309,7 +316,7 @@
         for (int i = 0; i < N; i++) {
             getChildAt(i).measure(widthChildSpec, heightChildSpec);
         }
-        int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth;
+        int width = calculateWidth(N);
         setMeasuredDimension(width, mPageIndicatorHeight);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index b515ce0..278352c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
@@ -56,6 +57,9 @@
     fun d(@CompileTimeConstant msg: String, arg: Any) {
         buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" })
     }
+    fun i(@CompileTimeConstant msg: String, arg: Any) {
+        buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+    }
 
     fun logTileAdded(tileSpec: String) {
         buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" })
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 6539cf3..86cc6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -23,10 +23,12 @@
 import androidx.compose.animation.graphics.res.animatedVectorResource
 import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
 import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
@@ -71,7 +73,7 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.background
+import com.android.compose.animation.Expandable
 import com.android.compose.theme.colorAttr
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
@@ -134,7 +136,7 @@
         }
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
+    @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
     @Composable
     private fun Tile(
         tile: TileViewModel,
@@ -147,28 +149,39 @@
                 .collectAsState(initial = tile.currentState.toUiState())
         val context = LocalContext.current
 
-        Row(
-            modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors),
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement = tileHorizontalArrangement(iconOnly)
+        Expandable(
+            color = colorAttr(state.colors.background),
+            shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
         ) {
-            val icon =
-                remember(state.icon) {
-                    state.icon.get().let {
-                        if (it is QSTileImpl.ResourceIcon) {
-                            Icon.Resource(it.resId, null)
-                        } else {
-                            Icon.Loaded(it.getDrawable(context), null)
+            Row(
+                modifier =
+                    modifier
+                        .combinedClickable(
+                            onClick = { tile.onClick(it) },
+                            onLongClick = { tile.onLongClick(it) }
+                        )
+                        .tileModifier(state.colors),
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = tileHorizontalArrangement(iconOnly),
+            ) {
+                val icon =
+                    remember(state.icon) {
+                        state.icon.get().let {
+                            if (it is QSTileImpl.ResourceIcon) {
+                                Icon.Resource(it.resId, null)
+                            } else {
+                                Icon.Loaded(it.getDrawable(context), null)
+                            }
                         }
                     }
-                }
-            TileContent(
-                label = state.label.toString(),
-                secondaryLabel = state.secondaryLabel.toString(),
-                icon = icon,
-                colors = state.colors,
-                iconOnly = iconOnly
-            )
+                TileContent(
+                    label = state.label.toString(),
+                    secondaryLabel = state.secondaryLabel?.toString(),
+                    icon = icon,
+                    colors = state.colors,
+                    iconOnly = iconOnly
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 065e89f..f0d7206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -175,6 +175,26 @@
             )
     }
 
+    /** Log with level [LogLevel.WARNING] */
+    fun logWarning(
+        tileSpec: TileSpec,
+        message: String,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(tileSpec.getLogTag(), LogLevel.WARNING, { str1 = message }, { str1!! })
+    }
+
+    /** Log with level [LogLevel.INFO] */
+    fun logInfo(
+        tileSpec: TileSpec,
+        message: String,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(tileSpec.getLogTag(), LogLevel.INFO, { str1 = message }, { str1!! })
+    }
+
     fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
         tileSpec
             .getLogBuffer()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 60469c0..b057476 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.dialog;
 
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+
 import static com.android.settingslib.mobile.MobileMappings.getIconKey;
 import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
 import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -190,7 +192,7 @@
     private DialogTransitionAnimator mDialogTransitionAnimator;
     private boolean mHasWifiEntries;
     private WifiStateWorker mWifiStateWorker;
-    private boolean mHasActiveSubId;
+    private boolean mHasActiveSubIdOnDds;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -298,7 +300,7 @@
                 mExecutor);
         // Listen the subscription changes
         mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
-        refreshHasActiveSubId();
+        refreshHasActiveSubIdOnDds();
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mOnSubscriptionsChangedListener);
         mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -428,7 +430,7 @@
         }
         boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager
                 .INVALID_SUBSCRIPTION_ID;
-        if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId)
+        if (!hasActiveSubIdOnDds() || (!isVoiceStateInService(mDefaultDataSubId)
                 && !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) {
             if (DEBUG) {
                 Log.d(TAG, "No carrier or service is out of service.");
@@ -901,23 +903,42 @@
     /**
      * @return whether there is the carrier item in the slice.
      */
-    boolean hasActiveSubId() {
+    boolean hasActiveSubIdOnDds() {
         if (isAirplaneModeEnabled() || mTelephonyManager == null) {
             return false;
         }
 
-        return mHasActiveSubId;
+        return mHasActiveSubIdOnDds;
     }
 
-    private void refreshHasActiveSubId() {
+    private static boolean isEmbeddedSubscriptionVisible(@NonNull SubscriptionInfo subInfo) {
+        if (subInfo.isEmbedded() && subInfo.getProfileClass() == PROFILE_CLASS_PROVISIONING) {
+            return false;
+        }
+        return true;
+    }
+
+    private void refreshHasActiveSubIdOnDds() {
         if (mSubscriptionManager == null) {
-            mHasActiveSubId = false;
+            mHasActiveSubIdOnDds = false;
             Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
             return;
         }
+        int dds = getDefaultDataSubscriptionId();
+        if (dds == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            mHasActiveSubIdOnDds = false;
+            Log.d(TAG, "DDS is INVALID_SUBSCRIPTION_ID");
+            return;
+        }
+        SubscriptionInfo ddsSubInfo = mSubscriptionManager.getActiveSubscriptionInfo(dds);
+        if (ddsSubInfo == null) {
+            mHasActiveSubIdOnDds = false;
+            Log.e(TAG, "Can't get DDS subscriptionInfo");
+            return;
+        }
 
-        mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
-        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
+        mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo);
+        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubIdOnDds);
     }
 
     /**
@@ -1209,7 +1230,7 @@
 
         @Override
         public void onSubscriptionsChanged() {
-            refreshHasActiveSubId();
+            refreshHasActiveSubIdOnDds();
             updateListener();
         }
     }
@@ -1306,6 +1327,7 @@
                     Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
                 }
                 mConfig = MobileMappings.Config.readConfig(context);
+                refreshHasActiveSubIdOnDds();
                 updateListener();
             } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
                 updateListener();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 1a881b6..c9c4443 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -429,7 +429,7 @@
         }
 
         boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
-        if (!mInternetDialogController.hasActiveSubId()
+        if (!mInternetDialogController.hasActiveSubIdOnDds()
                 && (!isWifiEnabled || !isCarrierNetworkActive)) {
             mMobileNetworkLayout.setVisibility(View.GONE);
             if (mSecondaryMobileNetworkLayout != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
index d1c8030..bd2f2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.qs.tiles.impl.location.domain.interactor
 
 import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
 import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
 import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 /** Observes location state changes providing the [LocationTileModel]. */
 class LocationTileDataInteractor
@@ -38,19 +38,7 @@
         user: UserHandle,
         triggers: Flow<DataUpdateTrigger>
     ): Flow<LocationTileModel> =
-        ConflatedCallbackFlow.conflatedCallbackFlow {
-            val initialValue = locationController.isLocationEnabled
-            trySend(LocationTileModel(initialValue))
-
-            val callback =
-                object : LocationController.LocationChangeCallback {
-                    override fun onLocationSettingsChanged(locationEnabled: Boolean) {
-                        trySend(LocationTileModel(locationEnabled))
-                    }
-                }
-            locationController.addCallback(callback)
-            awaitClose { locationController.removeCallback(callback) }
-        }
+        locationController.isLocationEnabledFlow().map { LocationTileModel(it) }
 
     override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
new file mode 100644
index 0000000..88bd224
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.util.time.DateFormatUtil
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes screen record state changes providing the [NightDisplayTileModel]. */
+class NightDisplayTileDataInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val dateFormatUtil: DateFormatUtil,
+    private val nightDisplayRepository: NightDisplayRepository,
+) : QSTileDataInteractor<NightDisplayTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<NightDisplayTileModel> =
+        nightDisplayRepository.nightDisplayState(user).map {
+            generateModel(
+                it.autoMode,
+                it.isActivated,
+                it.startTime,
+                it.endTime,
+                it.shouldForceAutoMode,
+                it.locationEnabled
+            )
+        }
+
+    /** This checks resources and there fore does not make a binder call. */
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(ColorDisplayManager.isNightDisplayAvailable(context))
+
+    private fun generateModel(
+        autoMode: Int,
+        isNightDisplayActivated: Boolean,
+        customStartTime: LocalTime?,
+        customEndTime: LocalTime?,
+        shouldForceAutoMode: Boolean,
+        locationEnabled: Boolean,
+    ): NightDisplayTileModel {
+        if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
+            return NightDisplayTileModel.AutoModeTwilight(
+                isNightDisplayActivated,
+                shouldForceAutoMode,
+                locationEnabled,
+            )
+        } else if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
+            return NightDisplayTileModel.AutoModeCustom(
+                isNightDisplayActivated,
+                shouldForceAutoMode,
+                customStartTime,
+                customEndTime,
+                dateFormatUtil.is24HourFormat,
+            )
+        } else { // auto mode off
+            return NightDisplayTileModel.AutoModeOff(isNightDisplayActivated, shouldForceAutoMode)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cee8c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.content.Intent
+import android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles night display tile clicks. */
+class NightDisplayTileUserActionInteractor
+@Inject
+constructor(
+    private val nightDisplayRepository: NightDisplayRepository,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val qsLogger: QSTileLogger,
+) : QSTileUserActionInteractor<NightDisplayTileModel> {
+    override suspend fun handleInput(input: QSTileInput<NightDisplayTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    // Enroll in forced auto mode if eligible.
+                    if (data.isEnrolledInForcedNightDisplayAutoMode) {
+                        nightDisplayRepository.setNightDisplayAutoMode(AUTO_MODE_CUSTOM_TIME, user)
+                        qsLogger.logInfo(spec, "Enrolled in forced night display auto mode")
+                    }
+                    nightDisplayRepository.setNightDisplayActivated(!data.isActivated, user)
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.expandable,
+                        Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS)
+                    )
+                }
+            }
+        }
+
+    companion object {
+        val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
new file mode 100644
index 0000000..6b1bd5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.model
+
+import java.time.LocalTime
+
+/** Data model for night display tile */
+sealed interface NightDisplayTileModel {
+    val isActivated: Boolean
+    val isEnrolledInForcedNightDisplayAutoMode: Boolean
+    data class AutoModeTwilight(
+        override val isActivated: Boolean,
+        override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+        val isLocationEnabled: Boolean
+    ) : NightDisplayTileModel
+    data class AutoModeCustom(
+        override val isActivated: Boolean,
+        override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+        val startTime: LocalTime?,
+        val endTime: LocalTime?,
+        val is24HourFormat: Boolean
+    ) : NightDisplayTileModel
+    data class AutoModeOff(
+        override val isActivated: Boolean,
+        override val isEnrolledInForcedNightDisplayAutoMode: Boolean
+    ) : NightDisplayTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
new file mode 100644
index 0000000..5c2dcfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import androidx.annotation.StringRes
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.DateTimeException
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import javax.inject.Inject
+
+/** Maps [NightDisplayTileModel] to [QSTileState]. */
+class NightDisplayTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+    private val logger: QSTileLogger,
+) : QSTileDataToStateMapper<NightDisplayTileModel> {
+    override fun map(config: QSTileConfig, data: NightDisplayTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.quick_settings_night_display_label)
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+            sideViewIcon = QSTileState.SideViewIcon.None
+
+            if (data.isActivated) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                val loadedIcon =
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme),
+                        contentDescription = null
+                    )
+                icon = { loadedIcon }
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                val loadedIcon =
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme),
+                        contentDescription = null
+                    )
+                icon = { loadedIcon }
+            }
+
+            secondaryLabel = getSecondaryLabel(data, resources)
+
+            contentDescription =
+                if (TextUtils.isEmpty(secondaryLabel)) label
+                else TextUtils.concat(label, ", ", secondaryLabel)
+        }
+
+    private fun getSecondaryLabel(
+        data: NightDisplayTileModel,
+        resources: Resources
+    ): CharSequence? {
+        when (data) {
+            is NightDisplayTileModel.AutoModeTwilight -> {
+                if (!data.isLocationEnabled) {
+                    return null
+                } else {
+                    return resources.getString(
+                        if (data.isActivated)
+                            R.string.quick_settings_night_secondary_label_until_sunrise
+                        else R.string.quick_settings_night_secondary_label_on_at_sunset
+                    )
+                }
+            }
+            is NightDisplayTileModel.AutoModeOff -> {
+                val subtitleArray = resources.getStringArray(R.array.tile_states_night)
+                return subtitleArray[
+                    if (data.isActivated) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE]
+            }
+            is NightDisplayTileModel.AutoModeCustom -> {
+                // User-specified time, approximated to the nearest hour.
+                @StringRes val toggleTimeStringRes: Int
+                val toggleTime: LocalTime
+                if (data.isActivated) {
+                    toggleTime = data.endTime ?: return null
+                    toggleTimeStringRes = R.string.quick_settings_secondary_label_until
+                } else {
+                    toggleTime = data.startTime ?: return null
+                    toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at
+                }
+
+                try {
+                    val formatter = if (data.is24HourFormat) formatter24Hour else formatter12Hour
+                    val formatArg = formatter.format(toggleTime)
+                    return resources.getString(toggleTimeStringRes, formatArg)
+                } catch (exception: DateTimeException) {
+                    logger.logWarning(spec, exception.message.toString())
+                    return null
+                }
+            }
+        }
+    }
+
+    private companion object {
+        val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+        val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+        val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 259a8bf..b971781 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -56,9 +56,8 @@
     }
 
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
-    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
-        val insets = super.onApplyWindowInsets(windowInsets)
-        this.windowInsets.value = insets
-        return insets
+    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
+        this.windowInsets.value = windowInsets
+        return windowInsets
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 70632d5..79218ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.animation.Animator;
@@ -49,6 +50,7 @@
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -108,6 +110,7 @@
     private final UiEventLogger mUiEventLogger;
     private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
     private final JavaAdapter mJavaAdapter;
+    private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
@@ -175,6 +178,7 @@
             UiEventLogger uiEventLogger,
             Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
             JavaAdapter javaAdapter,
+            Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
             Lazy<ShadeInteractor> shadeInteractorLazy,
             Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
             Lazy<SceneInteractor> sceneInteractorLazy,
@@ -182,6 +186,7 @@
         mUiEventLogger = uiEventLogger;
         mInteractionJankMonitorLazy = interactionJankMonitorLazy;
         mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
         mShadeInteractorLazy = shadeInteractorLazy;
         mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
         mSceneInteractorLazy = sceneInteractorLazy;
@@ -193,6 +198,14 @@
 
     @Override
     public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
+                (Boolean isFinishedInState) -> {
+                    if (isFinishedInState) {
+                        setLeaveOpenOnKeyguardHide(false);
+                    }
+                });
+
         mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
                 this::onShadeOrQsExpanded);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index c17da4b..0524589 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -32,6 +32,7 @@
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -56,10 +57,10 @@
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Binds;
@@ -209,14 +210,16 @@
     /** */
     @Provides
     @SysUISingleton
-    static ActivityTransitionAnimator provideActivityTransitionAnimator() {
-        return new ActivityTransitionAnimator();
+    static ActivityTransitionAnimator provideActivityTransitionAnimator(
+            @Main Executor mainExecutor) {
+        return new ActivityTransitionAnimator(mainExecutor);
     }
 
     /** */
     @Provides
     @SysUISingleton
-    static DialogTransitionAnimator provideDialogTransitionAnimator(IDreamManager dreamManager,
+    static DialogTransitionAnimator provideDialogTransitionAnimator(@Main Executor mainExecutor,
+            IDreamManager dreamManager,
             KeyguardStateController keyguardStateController,
             Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
             InteractionJankMonitor interactionJankMonitor,
@@ -243,7 +246,7 @@
             }
         };
         return new DialogTransitionAnimator(
-                callback, interactionJankMonitor, animationFeatureFlags);
+                mainExecutor, callback, interactionJankMonitor, animationFeatureFlags);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 6a38f8d..d2d0aaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -43,6 +45,7 @@
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -354,12 +357,13 @@
     @Override
     public long performRemoveAnimation(long duration, long delay, float translationDirection,
             boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
         if (mDrawingAppearAnimation) {
             startAppearAnimation(false /* isAppearing */, translationDirection,
-                    delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+                    delay, duration, onStartedRunnable, onFinishedRunnable, animationListener,
+                    clipSide);
         } else {
             if (onStartedRunnable != null) {
                 onStartedRunnable.run();
@@ -378,13 +382,13 @@
         mIsHeadsUpAnimation = isHeadsUpAppear;
         if (mDrawingAppearAnimation) {
             startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
-                    duration, null, null, null);
+                    duration, null, null, null, ClipSide.BOTTOM);
         }
     }
 
     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
             long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         mAnimationTranslationY = translationDirection * getActualHeight();
         cancelAppearAnimation();
         if (mAppearAnimationFraction == -1.0f) {
@@ -406,9 +410,16 @@
             mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
             targetValue = 0.0f;
         }
+
+        if (NotificationHeadsUpCycling.isEnabled()) {
+            // TODO(b/316404716): add avalanche filtering
+            mCurrentAppearInterpolator = Interpolators.LINEAR;
+        }
+
         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
                 targetValue);
-        if (NotificationsImprovedHunAnimation.isEnabled()) {
+        if (NotificationsImprovedHunAnimation.isEnabled()
+                || NotificationHeadsUpCycling.isEnabled()) {
             mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
         } else {
             mAppearAnimator.setInterpolator(Interpolators.LINEAR);
@@ -418,7 +429,12 @@
         mAppearAnimator.addUpdateListener(animation -> {
             mAppearAnimationFraction = (float) animation.getAnimatedValue();
             updateAppearAnimationAlpha();
-            updateAppearRect();
+            if (NotificationHeadsUpCycling.isEnabled()) {
+                // For cycling out, we want the HUN to be clipped from the top.
+                updateAppearRect(clipSide);
+            } else {
+                updateAppearRect();
+            }
             invalidate();
         });
         if (animationListener != null) {
@@ -426,7 +442,11 @@
         }
         // we need to apply the initial state already to avoid drawn frames in the wrong state
         updateAppearAnimationAlpha();
-        updateAppearRect();
+        if (NotificationHeadsUpCycling.isEnabled()) {
+            updateAppearRect(clipSide);
+        } else {
+            updateAppearRect();
+        }
         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mRunWithoutInterruptions;
 
@@ -508,14 +528,18 @@
         enableAppearDrawing(false);
     }
 
-    private void updateAppearRect() {
+    /**
+     * Update the View's Rect clipping to fit the appear animation
+     * @param clipSide Which side if view we want to clip from
+     */
+    private void updateAppearRect(ClipSide clipSide) {
         float interpolatedFraction =
-                NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+                NotificationsImprovedHunAnimation.isEnabled()
+                        || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
                         : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
         mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
-        final int actualHeight = getActualHeight();
-        float bottom = actualHeight * interpolatedFraction;
-
+        final int fullHeight = getActualHeight();
+        float height = fullHeight * interpolatedFraction;
         if (mTargetPoint != null) {
             int width = getWidth();
             float fraction = 1 - mAppearAnimationFraction;
@@ -524,13 +548,26 @@
                     mAnimationTranslationY
                             + (mAnimationTranslationY - mTargetPoint.y) * fraction,
                     width - (width - mTargetPoint.x) * fraction,
-                    actualHeight - (actualHeight - mTargetPoint.y) * fraction);
+                    fullHeight - (fullHeight - mTargetPoint.y) * fraction);
         } else {
-            setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
-                    bottom + mAppearAnimationTranslation);
+            if (clipSide == TOP) {
+                setOutlineRect(
+                        0,
+                        /* top= */ fullHeight - height,
+                        getWidth(),
+                        /* bottom= */ fullHeight
+                );
+            } else if (clipSide == BOTTOM) {
+                setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+                        height + mAppearAnimationTranslation);
+            }
         }
     }
 
+    private void updateAppearRect() {
+        updateAppearRect(ClipSide.BOTTOM);
+    }
+
     private float getInterpolatedAppearAnimationFraction() {
 
         if (mAppearAnimationFraction >= 0) {
@@ -540,11 +577,36 @@
     }
 
     private void updateAppearAnimationAlpha() {
-        float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction,
-                ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION);
-        float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION;
-        float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range;
-        setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha));
+        updateAppearAnimationContentAlpha(
+                mAppearAnimationFraction,
+                ALPHA_APPEAR_START_FRACTION,
+                ALPHA_APPEAR_END_FRACTION,
+                Interpolators.ALPHA_IN
+        );
+    }
+
+    /**
+     * Update the alpha value of the content view during the appear animation. We suppose that the
+     * content alpha changes from 0 to 1 during some part of the appear animation.
+     * @param appearFraction the current appearFraction, should be in the range of [0, 1], where
+     *                       1 represents fully appeared
+     * @param startFraction the appear fraction when the content view should be
+     *      *                    fully transparent
+     * @param endFraction the appear fraction when the content view should be
+     *                    fully in-transparent, should be greater or equals to startFraction
+     * @param interpolator the interpolator to update the alpha
+     */
+    private void updateAppearAnimationContentAlpha(
+            float appearFraction,
+            float startFraction,
+            float endFraction,
+            Interpolator interpolator
+    ) {
+        float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction,
+                endFraction);
+        float range = endFraction - startFraction;
+        float alpha = (contentAlphaProgress - startFraction) / range;
+        setContentAlpha(interpolator.getInterpolation(alpha));
     }
 
     private void setContentAlpha(float contentAlpha) {
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 23c0a0d..747cb3c 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
@@ -3076,7 +3076,7 @@
             boolean isHeadsUpAnimation,
             Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
             Animator anim = getTranslateViewAnimator(0f, null /* listener */);
             if (anim != null) {
@@ -3092,7 +3092,7 @@
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                null, onFinishedRunnable, animationListener);
+                                null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
                     }
                 });
                 anim.start();
@@ -3100,7 +3100,8 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
+                clipSide);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 05e8717..2af119f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -362,17 +362,17 @@
 
     /**
      * Perform a remove animation on this view.
-     * @param duration The duration of the remove animation.
-     * @param delay The delay of the animation
+     *
+     * @param duration             The duration of the remove animation.
+     * @param delay                The delay of the animation
      * @param translationDirection The direction value from [-1 ... 1] indicating in which the
      *                             animation should be performed. A value of -1 means that The
      *                             remove animation should be performed upwards,
      *                             such that the  child appears to be going away to the top. 1
      *                             Should mean the opposite.
-     * @param isHeadsUpAnimation Is this a headsUp animation.
-     * @param onFinishedRunnable A runnable which should be run when the animation is finished.
-     * @param animationListener An animation listener to add to the animation.
-     *
+     * @param isHeadsUpAnimation   Is this a headsUp animation.
+     * @param onFinishedRunnable   A runnable which should be run when the animation is finished.
+     * @param animationListener    An animation listener to add to the animation.
      * @return The additional delay, in milliseconds, that this view needs to add before the
      * animation starts.
      */
@@ -380,7 +380,12 @@
             long delay, float translationDirection, boolean isHeadsUpAnimation,
             Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener);
+            AnimatorListenerAdapter animationListener, ClipSide clipSide);
+
+    public enum ClipSide {
+        TOP,
+        BOTTOM
+    }
 
     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
         performAddAnimation(delay, duration, isHeadsUpAppear, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index 816e5c1..db3cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.app.Flags
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import javax.inject.Inject
 
 /**
@@ -27,11 +28,14 @@
     fun shouldApplyCompactStyle(): Boolean
 }
 
-class HeadsUpStyleProviderImpl @Inject constructor() : HeadsUpStyleProvider {
+class HeadsUpStyleProviderImpl
+@Inject
+constructor(private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore) :
+    HeadsUpStyleProvider {
 
-    /**
-     * TODO(b/270709257) This feature is under development. This method returns Compact when the
-     *   flag is enabled for fish fooding purpose.
-     */
-    override fun shouldApplyCompactStyle(): Boolean = Flags.compactHeadsUpNotification()
+    override fun shouldApplyCompactStyle(): Boolean {
+        // Use compact HUN for immersive mode.
+        return Flags.compactHeadsUpNotification() &&
+            statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 162e8af..291dc13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -252,7 +252,7 @@
             float translationDirection, boolean isHeadsUpAnimation,
             Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         // TODO: Use duration
         if (onStartedRunnable != null) {
             onStartedRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index 0344b32..d4f8ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -33,7 +33,12 @@
     /** Is the heads-up cycling animation enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationContentAlphaOptimization()
+        get() = Flags.notificationHeadsUpCycling()
+
+    /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
+    @JvmStatic
+    inline val animateTallToShort
+        get() = false
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index e520957..5f4e832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -293,6 +293,8 @@
     }
 
     String getAvalancheShowingHunKey() {
+        // If we don't have a previous showing hun, we don't consider the showing hun as avalanche
+        if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return "";
         return mAvalancheController.getShowingHunKey();
     }
 
@@ -300,6 +302,11 @@
         return mAvalancheController.getPreviousHunKey();
     }
 
+    boolean isNullAvalancheKey(String key) {
+        if (key == null || key.isEmpty()) return true;
+        return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null");
+    }
+
     void setOverExpansion(float overExpansion) {
         mOverExpansion = overExpansion;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5551ab4..bd7bd59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -70,13 +70,14 @@
     }
 
     override fun performRemoveAnimation(
-        duration: Long,
-        delay: Long,
-        translationDirection: Float,
-        isHeadsUpAnimation: Boolean,
-        onStartedRunnable: Runnable?,
-        onFinishedRunnable: Runnable?,
-        animationListener: AnimatorListenerAdapter?
+            duration: Long,
+            delay: Long,
+            translationDirection: Float,
+            isHeadsUpAnimation: Boolean,
+            onStartedRunnable: Runnable?,
+            onFinishedRunnable: Runnable?,
+            animationListener: AnimatorListenerAdapter?,
+            clipSide: ClipSide
     ): Long {
         return 0
     }
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 232b4e9..bfc7425 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
@@ -112,6 +112,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -151,7 +152,6 @@
 public class NotificationStackScrollLayout
         extends ViewGroup
         implements Dumpable, NotificationScrollView {
-
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
     private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
@@ -3144,6 +3144,11 @@
                 type = row.wasJustClicked()
                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
                         : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+                if (NotificationHeadsUpCycling.isEnabled()) {
+                    if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) {
+                        type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
+                    }
+                }
                 if (row.isChildInGroup()) {
                     // We can otherwise get stuck in there if it was just isolated
                     row.setHeadsUpAnimatingAway(false);
@@ -3164,6 +3169,11 @@
                     if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
                         // Our custom add animation
                         type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+                        if (NotificationHeadsUpCycling.isEnabled()) {
+                            if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) {
+                                type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+                            }
+                        }
                     } else {
                         // Normal add animation
                         type = AnimationEvent.ANIMATION_TYPE_ADD;
@@ -6135,6 +6145,22 @@
                         .animateTopInset()
                         .animateY()
                         .animateZ(),
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+                new AnimationFilter()
+                        .animateHeight()
+                        .animateTopInset()
+                        .animateY()
+                        .animateZ()
+                        .hasDelays(),
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+                new AnimationFilter()
+                        .animateHeight()
+                        .animateTopInset()
+                        .animateY()
+                        .animateZ()
+                        .hasDelays(),
         };
 
         static int[] LENGTHS = new int[]{
@@ -6186,6 +6212,12 @@
 
                 // ANIMATION_TYPE_EVERYTHING
                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+                StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+                StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
         };
 
         static final int ANIMATION_TYPE_ADD = 0;
@@ -6204,6 +6236,8 @@
         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
         static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
         static final int ANIMATION_TYPE_EVERYTHING = 15;
+        static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16;
+        static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17;
 
         final long eventStartTime;
         final ExpandableView mChangingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d0cebae..0fcfc4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 
 import java.util.ArrayList;
@@ -75,6 +76,7 @@
     private float mSmallCornerRadius;
     private float mLargeCornerRadius;
     private int mHeadsUpAppearHeightBottom;
+    private int mHeadsUpCyclingPadding;
 
     public StackScrollAlgorithm(
             Context context,
@@ -99,6 +101,8 @@
                 R.dimen.heads_up_status_bar_padding);
         mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
                 R.dimen.heads_up_appear_y_above_screen);
+        mHeadsUpCyclingPadding = context.getResources()
+                .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
         mPinnedZTranslationExtra = res.getDimensionPixelSize(
                 R.dimen.heads_up_pinned_elevation);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -348,7 +352,8 @@
                     && !firstHeadsUp
                     && (isHeadsUp || child.isHeadsUpAnimatingAway())
                     && newNotificationEnd > firstHeadsUpEnd
-                    && !ambientState.isShadeExpanded()) {
+                    && !ambientState.isShadeExpanded()
+                    && !skipClipBottomForCycling(child, ambientState)) {
                 // The bottom of this view is peeking out from under the previous view.
                 // Clip the part that is peeking out.
                 float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
@@ -370,6 +375,44 @@
         }
     }
 
+    /**
+     * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for
+     *         the hun cycling animation.
+     */
+    private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        if (!isCyclingOut(view, ambientState)) return false;
+        // skip bottom clipping if we animate the bottom line
+        return NotificationHeadsUpCycling.getAnimateTallToShort();
+    }
+
+    /**
+     * Whether the view is the hun that is cycling out by the notification avalanche.
+     */
+    public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        if (!(view instanceof ExpandableNotificationRow)) return false;
+        return isCyclingOut((ExpandableNotificationRow) view, ambientState);
+    }
+
+    /**
+     * Whether the row is the hun that is cycling out by the notification avalanche.
+     */
+    public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        String cyclingOutKey = ambientState.getAvalanchePreviousHunKey();
+        return row.getEntry().getKey().equals(cyclingOutKey);
+    }
+
+    /**
+     * Whether the row is the hun that is cycling in by the notification avalanche.
+     */
+    public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        String cyclingInKey = ambientState.getAvalancheShowingHunKey();
+        return row.getEntry().getKey().equals(cyclingInKey);
+    }
+
     /** Updates the dimmed and hiding sensitive states of the children. */
     private void updateDimmedAndHideSensitive(AmbientState ambientState,
             StackScrollAlgorithmState algorithmState) {
@@ -799,6 +842,7 @@
         }
 
         ExpandableNotificationRow topHeadsUpEntry = null;
+        int cyclingInHunHeight = -1;
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
             if (!(child instanceof ExpandableNotificationRow row)) {
@@ -839,6 +883,13 @@
                 childState.setYTranslation(
                         Math.max(childState.getYTranslation(), headsUpTranslation));
                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
+                if (NotificationHeadsUpCycling.isEnabled()) {
+                    if (isCyclingIn(row, ambientState)) {
+                        if (cyclingInHunHeight == -1) {
+                            cyclingInHunHeight = childState.height;
+                        }
+                    }
+                }
                 childState.hidden = false;
                 ExpandableViewState topState =
                         topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
@@ -860,6 +911,26 @@
                 }
             }
             if (row.isHeadsUpAnimatingAway()) {
+                if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) {
+                    // If the two HUNs in the cycling animation have different heights, we need
+                    // an extra y translation to align the animation.
+                    int extraTranslation;
+                    if (NotificationHeadsUpCycling.getAnimateTallToShort()) {
+                        if (cyclingInHunHeight > 0) {
+                            extraTranslation = cyclingInHunHeight - childState.height;
+                        } else {
+                            extraTranslation = 0;
+                        }
+                    } else {
+                        extraTranslation = cyclingInHunHeight >= childState.height
+                                ? cyclingInHunHeight - childState.height : 0;
+                    }
+                    extraTranslation += mHeadsUpCyclingPadding;
+                    float inSpaceTranslation = Math.max(childState.getYTranslation(),
+                            headsUpTranslation);
+                    childState.setYTranslation(inSpaceTranslation + extraTranslation);
+                    cyclingInHunHeight = -1;
+                } else
                 if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
                     if (shouldHunAppearFromBottom(ambientState, childState)) {
                         // move to the bottom of the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5963d35..5dc5449 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
 
@@ -57,6 +59,7 @@
     public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+    public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400;
     public static final int ANIMATION_DURATION_FOLD_TO_AOD =
             AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
     public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
@@ -68,6 +71,8 @@
 
     @VisibleForTesting int mGoToFullShadeAppearingTranslation;
     @VisibleForTesting float mHeadsUpAppearStartAboveScreen;
+    // Padding between the old and new heads up notifications for the hun cycling animation
+    private float mHeadsUpCyclingPadding;
     private final ExpandableViewState mTmpState = new ExpandableViewState();
     private final AnimationProperties mAnimationProperties;
     public NotificationStackScrollLayout mHostLayout;
@@ -125,6 +130,8 @@
                         R.dimen.go_to_full_shade_appearing_translation);
         mHeadsUpAppearStartAboveScreen = context.getResources()
                 .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+        mHeadsUpCyclingPadding = context.getResources()
+                .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
     }
 
     protected void setLogger(StackStateLogger logger) {
@@ -449,7 +456,8 @@
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        startAnimation, postAnimation, getGlobalAnimationFinishedListener());
+                        startAnimation, postAnimation, getGlobalAnimationFinishedListener(),
+                        ExpandableView.ClipSide.BOTTOM);
                 needsCustomAnimation = true;
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
@@ -464,6 +472,27 @@
                     .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
                 row.prepareExpansionChanged();
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) {
+                mHeadsUpAppearChildren.add(changingView);
+
+                mTmpState.copyFrom(changingView.getViewState());
+                mTmpState.setYTranslation(changingView.getViewState().getYTranslation()
+                        + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom));
+                mTmpState.applyToView(changingView);
+
+                // TODO(b/339519404): use a different interpolator
+                Runnable onAnimationEnd = null;
+                if (loggable) {
+                    // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+                    // normal ADD animations, which would not be logged here.
+                    String finalKey = key;
+                    mLogger.logHUNViewAppearing(key);
+                    onAnimationEnd = () -> {
+                        mLogger.appearAnimationEnded(finalKey);
+                    };
+                }
+                changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
+                        /* isHeadsUpAppear= */ true, onAnimationEnd);
             } else if (NotificationsImprovedHunAnimation.isEnabled()
                     && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
                 mHeadsUpAppearChildren.add(changingView);
@@ -486,6 +515,87 @@
                 }
                 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
                         /* isHeadsUpAppear= */ true, onAnimationEnd);
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) {
+                mHeadsUpDisappearChildren.add(changingView);
+                Runnable endRunnable = null;
+                mTmpState.copyFrom(changingView.getViewState());
+
+                if (changingView.getParent() == null) {
+                    // This notification was actually removed, so we need to add it
+                    // transiently
+                    mHostLayout.addTransientView(changingView, 0);
+                    changingView.setTransientContainer(mHostLayout);
+                    // TODO(b/316404716): remove the hard-coded height
+                    // StackScrollAlgorithm cannot find this view because it has been removed
+                    // from the NSSL. To correctly translate the view to the top or bottom of
+                    // the screen (where it animated from), we need to update its translation.
+                    mTmpState.setYTranslation(
+                            mTmpState.getYTranslation() + 10
+                    );
+                    endRunnable = changingView::removeFromTransientContainer;
+                }
+
+                boolean needsAnimation = true;
+                if (changingView instanceof ExpandableNotificationRow) {
+                    ExpandableNotificationRow row =
+                            (ExpandableNotificationRow) changingView;
+                    if (row.isDismissed()) {
+                        needsAnimation = false;
+                    }
+                }
+                if (needsAnimation) {
+                    // We need to add the global animation listener, since once no animations are
+                    // running anymore, the panel will instantly hide itself. We need to wait until
+                    // the animation is fully finished for this though.
+                    final Runnable tmpEndRunnable = endRunnable;
+                    Runnable postAnimation;
+                    Runnable startAnimation;
+                    if (loggable) {
+                        String finalKey1 = key;
+                        final boolean finalIsHeadsUp = isHeadsUp;
+                        final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT";
+                        startAnimation = () -> {
+                            mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(true);
+                        };
+                        postAnimation = () -> {
+                            mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+
+                        };
+                    } else {
+                        postAnimation = () -> {
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+                        };
+                        startAnimation = () -> {
+                            changingView.setInRemovalAnimation(true);
+                        };
+                    }
+                    long removeAnimationDelay = changingView.performRemoveAnimation(
+                            ANIMATION_DURATION_HEADS_UP_CYCLING,
+                            /* delay= */ 0,
+                            // It's a shame that translationDirection isn't where we do the y
+                            // translation, the actual translation is in StackScrollAlgorithm.
+                            /* translationDirection= */ 0.0f,
+                            /* isHeadsUpAnimation= */ true,
+                            startAnimation, postAnimation,
+                            getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP);
+                    mAnimationProperties.delay += removeAnimationDelay;
+                    mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING;
+                    mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+                            Interpolators.LINEAR);
+                    mAnimationProperties.getAnimationFilter().animateY = true;
+                    mTmpState.animateTo(changingView, mAnimationProperties);
+                } else if (endRunnable != null) {
+                    endRunnable.run();
+                }
+                needsCustomAnimation |= needsAnimation;
             } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
                 NotificationsImprovedHunAnimation.assertInLegacyMode();
                 // This item is added, initialize its properties.
@@ -565,21 +675,21 @@
                             }
                         };
                     } else {
+                        startAnimation = () -> {
+                            changingView.setInRemovalAnimation(true);
+                        };
                         postAnimation = () -> {
                             changingView.setInRemovalAnimation(false);
                             if (tmpEndRunnable != null) {
                                 tmpEndRunnable.run();
                             }
                         };
-                        startAnimation = () -> {
-                            changingView.setInRemovalAnimation(true);
-                        };
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */,
                             startAnimation, postAnimation,
-                            getGlobalAnimationFinishedListener());
+                            getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
                     mAnimationProperties.delay += removeAnimationDelay;
                     if (NotificationsImprovedHunAnimation.isEnabled()) {
                         mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
@@ -607,6 +717,38 @@
         return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
     }
 
+    /**
+     * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+     * @return The start y translation of the HUN cycling in animation
+     */
+    private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) {
+        if (headsUpFromBottom) {
+            // start from the bottom of the screen
+            return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding;
+        }
+        // start from the top of the screen
+        return -mHeadsUpCyclingPadding;
+    }
+
+    /**
+     * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+     * @param oldHunHeight Height of the old HUN
+     * @param newHunHeight Height of the new HUN
+     * @return The y translation target value of the HUN cycling out animation
+     */
+    private float getHeadsUpCyclingOutYTranslation(
+            boolean headsUpFromBottom,
+            int oldHunHeight,
+            int newHunHeight
+    ) {
+        final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight;
+        if (headsUpFromBottom) {
+            // start from the bottom of the screen
+            return mHeadsUpAppearHeightBottom - translationDistance;
+        }
+        return translationDistance;
+    }
+
     public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
             final boolean isRubberbanded) {
         final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
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 be6bef7..23674b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2184,7 +2184,9 @@
         }
         if (mStatusBarStateController.leaveOpenOnKeyguardHide()) {
             if (!mStatusBarStateController.isKeyguardRequested()) {
-                mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+                if (!MigrateClocksToBlueprint.isEnabled()) {
+                    mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+                }
             }
             long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
             mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
index d9d909a..fc54f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -33,6 +33,18 @@
      */
     val isOpportunistic: Boolean = false,
 
+    /**
+     * True if this subscription **only** supports non-terrestrial networks (NTN) and false
+     * otherwise. (non-terrestrial == satellite)
+     *
+     * Note that we intend to filter these subscriptions out, because these connections are actually
+     * supported by
+     * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. See
+     * [com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor] for
+     * the filtering.
+     */
+    val isExclusivelyNonTerrestrial: Boolean = false,
+
     /** Subscriptions in the same group may be filtered or treated as a single subscription */
     val groupUuid: ParcelUuid? = null,
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2278597..425c58b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.flow.StateFlow
 
@@ -76,7 +77,17 @@
      */
     val isInService: StateFlow<Boolean>
 
-    /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */
+    /**
+     * True if this subscription is actively connected to a non-terrestrial network and false
+     * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork].
+     *
+     * Notably: This value reflects that this subscription is **currently** using a non-terrestrial
+     * network, because some subscriptions can switch between terrestrial and non-terrestrial
+     * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is
+     * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change
+     * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial]
+     * will stay constant.
+     */
     val isNonTerrestrial: StateFlow<Boolean>
 
     /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 5d91ef3..0073e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -424,6 +424,7 @@
         SubscriptionModel(
             subscriptionId = subscriptionId,
             isOpportunistic = isOpportunistic,
+            isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork,
             groupUuid = groupUuid,
             carrierName = carrierName.toString(),
             profileClass = profileClass,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index d555c47..91d7ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -172,21 +172,33 @@
     private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
         mobileConnectionsRepo.subscriptions
 
-    /**
-     * Any filtering that we can do based purely on the info of each subscription. Currently this
-     * only applies the ProfileClass-based filter, but if we need other they can go here
-     */
+    /** Any filtering that we can do based purely on the info of each subscription individually. */
     private val subscriptionsBasedFilteredSubs =
-        unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged()
+        unfilteredSubscriptions
+            .map { it.filterBasedOnProvisioning().filterBasedOnNtn() }
+            .distinctUntilChanged()
 
-    private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> =
+    private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> =
         if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
-            subs
+            this
         } else {
-            subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+            this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
         }
 
     /**
+     * Subscriptions that exclusively support non-terrestrial networks should **never** directly
+     * show any iconography in the status bar. These subscriptions only exist to provide a backing
+     * for the device-based satellite connections, and the iconography for those connections are
+     * already being handled in
+     * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We
+     * need to filter out those subscriptions here so we guarantee the subscription never turns into
+     * an icon. See b/336881301.
+     */
+    private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> {
+        return this.filter { !it.isExclusivelyNonTerrestrial }
+    }
+
+    /**
      * Generally, SystemUI wants to show iconography for each subscription that is listed by
      * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
      * show a single representation of the pair of subscriptions. The docs define opportunistic as:
@@ -204,12 +216,8 @@
                 subscriptionsBasedFilteredSubs,
                 mobileConnectionsRepo.activeMobileDataSubscriptionId,
                 connectivityRepository.vcnSubId,
-            ) { unfilteredSubs, activeId, vcnSubId ->
-                filterSubsBasedOnOpportunistic(
-                    unfilteredSubs,
-                    activeId,
-                    vcnSubId,
-                )
+            ) { preFilteredSubs, activeId, vcnSubId ->
+                filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
             }
             .distinctUntilChanged()
             .logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 2670a95..fa8a7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -253,6 +253,7 @@
 
         if (nextList.isEmpty()) {
             log { "NO MORE TO SHOW" }
+            previousHunKey = ""
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
new file mode 100644
index 0000000..ee1b565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun LocationController.isLocationEnabledFlow(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val locationCallback =
+                object : LocationController.LocationChangeCallback {
+                    override fun onLocationSettingsChanged(locationEnabled: Boolean) {
+                        trySend(locationEnabled)
+                    }
+                }
+            addCallback(locationCallback)
+            awaitClose { removeCallback(locationCallback) }
+        }
+        .onStart { emit(isLocationEnabled) }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e72027a..6f550ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -371,7 +371,7 @@
         }
 
     @Test
-    fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() =
+    fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
             whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
@@ -434,6 +434,27 @@
         }
 
     @Test
+    fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.DOZING))
+                    .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToDozingTransition(this)
+            transitionStep.value =
+                    TransitionStep(
+                            from = KeyguardState.LOCKSCREEN,
+                            to = KeyguardState.DOZING,
+                            transitionState = TransitionState.STARTED,
+                    )
+            yield()
+
+            verify(animations, times(2)).doze(1f)
+
+            job.cancel()
+        }
+
+    @Test
     fun unregisterListeners_validate() =
         runBlocking(IMMEDIATE) {
             underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 41974f4..8e4c155 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -46,7 +46,8 @@
 @RunWithLooper
 class ActivityTransitionAnimatorTest : SysuiTestCase() {
     private val transitionContainer = LinearLayout(mContext)
-    private val testTransitionAnimator = fakeTransitionAnimator()
+    private val mainExecutor = context.mainExecutor
+    private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
     @Mock lateinit var callback: ActivityTransitionAnimator.Callback
     @Mock lateinit var listener: ActivityTransitionAnimator.Listener
     @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -59,9 +60,10 @@
     fun setup() {
         activityTransitionAnimator =
             ActivityTransitionAnimator(
+                mainExecutor,
                 testTransitionAnimator,
                 testTransitionAnimator,
-                disableWmTimeout = true
+                disableWmTimeout = true,
             )
         activityTransitionAnimator.callback = callback
         activityTransitionAnimator.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
index d84a578..e14762cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
@@ -156,6 +156,7 @@
     fun testActivityLaunchWhenLockedWithoutAlternateAuth() {
         val dialogTransitionAnimator =
                 fakeDialogTransitionAnimator(
+                        mainExecutor = mContext.mainExecutor,
                         isUnlocked = false,
                         isShowingAlternateAuthOnUnlock = false,
                         interactionJankMonitor = kosmos.interactionJankMonitor)
@@ -166,6 +167,7 @@
     @Test
     fun testActivityLaunchWhenLockedWithAlternateAuth() {
         val dialogTransitionAnimator = fakeDialogTransitionAnimator(
+                mainExecutor = mContext.mainExecutor,
                 isUnlocked = false,
                 isShowingAlternateAuthOnUnlock = true,
                 interactionJankMonitor = kosmos.interactionJankMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index e64df90..259ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -25,6 +25,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,9 +60,11 @@
             )
     }
 
+    private val kosmos = Kosmos()
     private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
     private val transitionAnimator =
         TransitionAnimator(
+            kosmos.fakeExecutor,
             ActivityTransitionAnimator.TIMINGS,
             ActivityTransitionAnimator.INTERPOLATORS
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 2172bc5..8695c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -5,12 +5,12 @@
 import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
 import com.android.systemui.biometrics.promptInfo
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -110,7 +110,7 @@
                     it.description = description
                     it.subtitle = subtitle
                 },
-                kind = Utils.CREDENTIAL_PIN,
+                kind = PromptKind.Pin,
                 userId = USER_ID,
                 challenge = OPERATION_ID,
                 opPackageName = OP_PACKAGE_NAME
@@ -135,7 +135,7 @@
                     it.subtitle = subtitle
                     it.contentView = contentView
                 },
-                kind = Utils.CREDENTIAL_PIN,
+                kind = PromptKind.Pin,
                 userId = USER_ID,
                 challenge = OPERATION_ID,
                 opPackageName = OP_PACKAGE_NAME
@@ -163,7 +163,7 @@
                     it.subtitle = subtitle
                     it.contentView = contentView
                 },
-                kind = Utils.CREDENTIAL_PIN,
+                kind = PromptKind.Pin,
                 userId = USER_ID,
                 challenge = OPERATION_ID,
                 opPackageName = OP_PACKAGE_NAME
@@ -171,13 +171,13 @@
             assertThat(showTitleOnly).isFalse()
         }
 
-    @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+    @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin)
 
-    @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+    @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password)
 
-    @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+    @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern)
 
-    private fun useCredentialForPrompt(kind: Int) =
+    private fun useCredentialForPrompt(kind: PromptKind) =
         testScope.runTest {
             val isStealth = false
             credentialInteractor.stealthMode = isStealth
@@ -211,11 +211,10 @@
             assertThat(prompt)
                 .isInstanceOf(
                     when (kind) {
-                        Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
-                        Utils.CREDENTIAL_PASSWORD ->
+                        PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java
+                        PromptKind.Password ->
                             BiometricPromptRequest.Credential.Password::class.java
-                        Utils.CREDENTIAL_PATTERN ->
-                            BiometricPromptRequest.Credential.Pattern::class.java
+                        PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java
                         else -> throw Exception("wrong kind")
                     }
                 )
@@ -341,6 +340,28 @@
 
             job.cancel()
         }
+
+    /** Update the current request to use credential-based authentication instead of biometrics. */
+    private fun PromptCredentialInteractor.useCredentialsForAuthentication(
+        promptInfo: PromptInfo,
+        kind: PromptKind,
+        userId: Int,
+        challenge: Long,
+        opPackageName: String,
+    ) {
+        biometricPromptRepository.setPrompt(
+            promptInfo,
+            userId,
+            challenge,
+            kind,
+            opPackageName,
+        )
+    }
+
+    /** Unset the current authentication request. */
+    private fun PromptCredentialInteractor.resetPrompt() {
+        biometricPromptRepository.unsetPrompt()
+    }
 }
 
 private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 2817780..c308507 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -143,21 +142,20 @@
     }
 
     @Test
-    fun usePinCredentialAndReset() =
-        testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) }
+    fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) }
 
     @Test
     fun usePatternCredentialAndReset() =
-        testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) }
+        testScope.runTest { useCredentialAndReset(PromptKind.Pattern) }
 
     @Test
     fun usePasswordCredentialAndReset() =
-        testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) }
+        testScope.runTest { useCredentialAndReset(PromptKind.Password) }
 
-    private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) {
+    private fun TestScope.useCredentialAndReset(kind: PromptKind) {
         setUserCredentialType(
-            isPin = kind == Utils.CREDENTIAL_PIN,
-            isPassword = kind == Utils.CREDENTIAL_PASSWORD,
+            isPin = kind == PromptKind.Pin,
+            isPassword = kind == PromptKind.Password,
         )
 
         val info =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 5caa146..0d01472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -16,97 +16,95 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
-class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class DefaultUdfpsTouchOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
+        }
+    private val testScope = kosmos.testScope
+
     @Captor
     private lateinit var sysuiDialogListenerCaptor: ArgumentCaptor<SystemUIDialogManager.Listener>
-    private var systemUIDialogManager: SystemUIDialogManager = mock()
+    private var systemUIDialogManager = kosmos.systemUIDialogManager
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: DefaultUdfpsTouchOverlayViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return parameterizeSceneContainerFlag()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        underTest =
+            DefaultUdfpsTouchOverlayViewModel(
+                kosmos.shadeInteractor,
+                systemUIDialogManager,
+            )
     }
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-                BiometricsDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> {
-        val keyguardRepository: FakeKeyguardRepository
-        val shadeRepository: FakeShadeRepository
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
-
-    private fun TestComponent.shadeExpanded(expanded: Boolean) {
+    private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setLegacyShadeExpansion(1f)
-            shadeRepository.setLegacyShadeTracking(false)
-            shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
+            shadeTestUtil.setShadeExpansion(1f)
+            shadeTestUtil.setTracking(false)
+            shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
+            shadeTestUtil.setShadeExpansion(0f)
+            shadeTestUtil.setTracking(false)
+            shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
         }
     }
 
-    private val testComponent: TestComponent =
-        DaggerDefaultUdfpsTouchOverlayViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks = TestMocksModule(systemUIDialogManager = systemUIDialogManager),
-            )
-
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun shadeNotExpanded_noDialogShowing_shouldHandleTouchesTrue() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
             runCurrent()
 
@@ -120,7 +118,7 @@
 
     @Test
     fun shadeNotExpanded_dialogShowing_shouldHandleTouchesFalse() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
             runCurrent()
 
@@ -134,7 +132,7 @@
 
     @Test
     fun shadeExpanded_noDialogShowing_shouldHandleTouchesFalse() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
             runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 28cbcb4..4bcd9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothDevice
-import android.content.pm.PackageInfo
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.media.AudioManager
 import android.platform.test.annotations.DisableFlags
@@ -25,7 +25,6 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
-import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.flags.Flags
 import com.android.systemui.SysuiTestCase
@@ -120,11 +119,10 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo())
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
 
@@ -144,11 +142,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+    fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
-        `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
-            .thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo().also { it.enabled = false })
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
 
@@ -158,12 +156,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+    fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
             .thenThrow(PackageManager.NameNotFoundException("Test!"))
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
@@ -228,11 +224,10 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo())
         `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(bluetoothDevice.isConnected).thenReturn(true)
         audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -254,11 +249,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+    fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
-        `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
-            .thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo().also { it.enabled = false })
         `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(bluetoothDevice.isConnected).thenReturn(true)
         audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -269,12 +264,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+    fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
             .thenThrow(PackageManager.NameNotFoundException("Test!"))
         `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(bluetoothDevice.isConnected).thenReturn(true)
@@ -317,7 +310,7 @@
     companion object {
         const val DEVICE_NAME = "DeviceName"
         const val CONNECTION_SUMMARY = "ConnectionSummary"
-        private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+        private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager"
         private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index bed05ee..cde7a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -30,7 +30,6 @@
 import java.nio.charset.Charset
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -102,12 +101,6 @@
         assertThat(dataRead).isEqualTo(newDataToWrite)
     }
 
-    @Ignore("Ignored until we figure out why it is flaky b/336561027")
-    @Test(expected = FileNotFoundException::class)
-    fun read_fileNotFoundException() {
-        underTest.readBytesFromDisk()
-    }
-
     @Test(expected = FileNotFoundException::class)
     fun clear_returnsTrueWhenFileDeleted() {
         // Write bytes to disk
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 6a0462b..c39c3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.deviceentry.domain.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
@@ -24,7 +24,9 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -34,19 +36,22 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,8 +64,27 @@
     private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
-    private val shadeRepository = kosmos.fakeShadeRepository
-    private val underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: DeviceEntryUdfpsAccessibilityOverlayViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+    }
 
     @Test
     fun visible() =
@@ -142,7 +166,7 @@
         )
 
         // Shade not expanded
-        shadeRepository.qsExpansion.value = 0f
-        shadeRepository.lockscreenShadeExpansion.value = 0f
+        shadeTestUtil.setQsExpansion(0f)
+        shadeTestUtil.setLockscreenShadeExpansion(0f)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1dc58d1..687e91a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.keyguard.KeyguardSecurityModel
@@ -29,7 +30,9 @@
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -46,7 +49,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
@@ -61,13 +65,14 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 /**
  * Class for testing user journeys through the interactors. They will all be activated during setup,
@@ -75,8 +80,8 @@
  */
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(JUnit4::class)
-class KeyguardTransitionScenariosTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
@@ -87,7 +92,7 @@
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
     private var commandQueue = kosmos.fakeCommandQueue
-    private val shadeRepository = kosmos.fakeShadeRepository
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
     private lateinit var featureFlags: FakeFeatureFlags
 
@@ -112,6 +117,18 @@
     private val communalInteractor = kosmos.communalInteractor
     private val dockManager = kosmos.fakeDockManager
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -119,9 +136,11 @@
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-        mSetFlagsRule.disableFlags(
-            Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
-        )
+        if (!SceneContainerFlag.isEnabled) {
+            mSetFlagsRule.disableFlags(
+                Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+            )
+        }
         featureFlags = FakeFeatureFlags()
 
         fromLockscreenTransitionInteractor.start()
@@ -210,6 +229,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToDreaming() =
         testScope.runTest {
             // GIVEN a device that is not dreaming or dozing
@@ -238,6 +258,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToDreamingLockscreenHosted() =
         testScope.runTest {
             // GIVEN a device that is not dreaming or dozing
@@ -527,6 +548,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun dozingToGoneWithUnlock() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -706,6 +728,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun goneToLockscreen() =
         testScope.runTest {
             // GIVEN a prior transition has run to GONE
@@ -755,6 +778,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun goneToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to GONE
@@ -897,6 +921,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun alternateBouncerToGone() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1135,6 +1160,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToGone() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1165,6 +1191,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToLockscreen() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1193,6 +1220,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToGlanceableHub() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1229,6 +1257,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToGlanceableHubWhenInitiallyOnHub() =
         testScope.runTest {
             // GIVEN a device on lockscreen and communal is available
@@ -1314,6 +1343,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun primaryBouncerToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1339,6 +1369,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun dozingToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -1364,6 +1395,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun dreamingToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
@@ -1484,6 +1516,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1507,6 +1540,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun aodToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to AOD
@@ -1553,6 +1587,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToOccluded_fromCameraGesture() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1586,6 +1621,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToPrimaryBouncerDragging() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1595,8 +1631,8 @@
             // GIVEN the keyguard is showing locked
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             runCurrent()
-            shadeRepository.setLegacyShadeTracking(true)
-            shadeRepository.setLegacyShadeExpansion(.9f)
+            shadeTestUtil.setTracking(true)
+            shadeTestUtil.setShadeExpansion(.9f)
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1613,8 +1649,8 @@
             // WHEN the user stops dragging and shade is back to expanded
             clearInvocations(transitionRepository)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
-            shadeRepository.setLegacyShadeTracking(false)
-            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setTracking(false)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1803,6 +1839,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun glanceableHubToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 0bca367..f61ddeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
@@ -150,6 +151,51 @@
             assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
         }
 
+    fun accessibilityDelegateHint_accessibilityNotEnabled() =
+        testScope.runTest {
+            val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+            kosmos.fakeAccessibilityRepository.isEnabled.value = false
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+        }
+
+    @Test
+    fun accessibilityDelegateHint_accessibilityEnabled_locked() =
+        testScope.runTest {
+            val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+            kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+            // interactive lock icon
+            keyguardRepository.setKeyguardDismissible(false)
+            fingerprintPropertyRepository.supportsUdfps()
+
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE)
+
+            // non-interactive lock icon
+            keyguardRepository.setKeyguardDismissible(false)
+            fingerprintPropertyRepository.supportsRearFps()
+
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+        }
+
+    @Test
+    fun accessibilityDelegateHint_accessibilityEnabled_unlocked() =
+        testScope.runTest {
+            val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+            kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+            // interactive unlock icon
+            keyguardRepository.setKeyguardDismissible(true)
+            fingerprintPropertyRepository.supportsUdfps()
+            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+            runCurrent()
+
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER)
+        }
+
     private fun deviceEntryIconTransitionAlpha(alpha: Float) {
         deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1881a9e..16421a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -88,7 +88,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -115,7 +115,7 @@
     private val kosmos = testKosmos()
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 0c98cff..768d446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -53,7 +53,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val underTest = kosmos.keyguardClockViewModel
@@ -67,7 +67,7 @@
     var faceConfig = ClockFaceConfig()
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2536a93..9798562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -5,6 +5,7 @@
 import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
 import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT;
 import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -217,6 +218,8 @@
         when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
         when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
         when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
             .thenReturn(mSystemUIToast);
         when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -1083,19 +1086,34 @@
     }
 
     @Test
-    public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
-        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+    public void hasActiveSubIdOnDds_noDds_returnFalse() {
+        when(SubscriptionManager.getDefaultDataSubscriptionId())
+                .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
         mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
 
-        assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+        assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
     }
 
     @Test
-    public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
-        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+    public void hasActiveSubIdOnDds_activeDds_returnTrue() {
         mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
 
-        assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+        assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isTrue();
+    }
+
+    @Test
+    public void hasActiveSubIdOnDds_activeDdsAndHasProvisioning_returnFalse() {
+        when(SubscriptionManager.getDefaultDataSubscriptionId())
+                .thenReturn(SUB_ID);
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(info.isEmbedded()).thenReturn(true);
+        when(info.getProfileClass()).thenReturn(PROFILE_CLASS_PROVISIONING);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
+
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
     }
 
     private String getResourcesString(String name) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 6f88891..aefcc87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -251,7 +251,7 @@
         // Mobile network should be gone if the list of active subscriptionId is null.
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-        when(mInternetDialogController.hasActiveSubId()).thenReturn(false);
+        when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false);
 
         mInternetDialogDelegate.updateDialog(true);
 
@@ -336,7 +336,7 @@
 
     @Test
     public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
         mMobileToggleSwitch.setChecked(false);
@@ -348,7 +348,7 @@
 
     @Test
     public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
         mMobileToggleSwitch.setChecked(false);
@@ -361,7 +361,7 @@
     @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
         mInternetDialogDelegate.dismissDialog();
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         createInternetDialog();
         // The preconditions WiFi ON and Internet WiFi are already in setUp()
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
@@ -522,7 +522,7 @@
     public void updateDialog_showSecondaryDataSub() {
         mInternetDialogDelegate.dismissDialog();
         doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
         createInternetDialog();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 3793970..5b47c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -446,6 +446,7 @@
                 mUiEventLogger,
                 () -> mKosmos.getInteractionJankMonitor(),
                 mJavaAdapter,
+                () -> mKeyguardTransitionInteractor,
                 () -> mShadeInteractor,
                 () -> mKosmos.getDeviceUnlockedInteractor(),
                 () -> mKosmos.getSceneInteractor(),
@@ -600,6 +601,7 @@
                                 new UiEventLoggerFake(),
                                 () -> mKosmos.getInteractionJankMonitor(),
                                 mJavaAdapter,
+                                () -> mKeyguardTransitionInteractor,
                                 () -> mShadeInteractor,
                                 () -> mKosmos.getDeviceUnlockedInteractor(),
                                 () -> mKosmos.getSceneInteractor(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index a867b0f..45d0102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -102,7 +102,7 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @RunWithLooper(setAsMainLooper = true)
-class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @Mock private lateinit var view: NotificationShadeWindowView
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@@ -160,7 +160,7 @@
     private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 347620a..83ad18b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -55,7 +55,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
-class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -79,7 +79,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
new file mode 100644
index 0000000..5e50af3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.notification.row
+
+import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HeadsUpStyleProviderImplTest : SysuiTestCase() {
+
+    @Rule @JvmField val setFlagsRule = SetFlagsRule()
+
+    private lateinit var statusBarModeRepositoryStore: FakeStatusBarModeRepository
+    private lateinit var headsUpStyleProvider: HeadsUpStyleProviderImpl
+
+    @Before
+    fun setUp() {
+        statusBarModeRepositoryStore = FakeStatusBarModeRepository()
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+        headsUpStyleProvider = HeadsUpStyleProviderImpl(statusBarModeRepositoryStore)
+    }
+
+    @Test
+    @DisableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+    fun shouldApplyCompactStyle_returnsFalse_whenCompactFlagDisabled() {
+        assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+    }
+
+    @Test
+    @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+    fun shouldApplyCompactStyle_returnsTrue_whenImmersiveModeEnabled() {
+        // GIVEN
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+        // THEN
+        assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+    fun shouldApplyCompactStyle_returnsFalse_whenImmersiveModeDisabled() {
+        // GIVEN
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = false
+
+        // THEN
+        assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 4f0f91a..926c35f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -134,7 +134,8 @@
                 /* isHeadsUpAnimation= */ eq(true),
                 /* onStartedRunnable= */ any(),
                 /* onFinishedRunnable= */ runnableCaptor.capture(),
-                /* animationListener= */ any()
+                /* animationListener= */ any(),
+                /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
             )
 
         animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 9b4f931..cb40f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -66,7 +66,7 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val featureFlags = FakeFeatureFlags()
@@ -92,7 +92,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Captor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b5525b1..36df61d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -295,6 +295,50 @@
         }
 
     @Test
+    fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
+
+            val onlyNtnSub =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true)
+                    whenever(it.subscriptionId).thenReturn(45)
+                    whenever(it.groupUuid).thenReturn(GROUP_1)
+                    whenever(it.carrierName).thenReturn("NTN only")
+                    whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+                }
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(onlyNtnSub))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
+        }
+
+    @Test
+    fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
+
+            val notOnlyNtnSub =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false)
+                    whenever(it.subscriptionId).thenReturn(45)
+                    whenever(it.groupUuid).thenReturn(GROUP_1)
+                    whenever(it.carrierName).thenReturn("NTN only")
+                    whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+                }
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(notOnlyNtnSub))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
+        }
+
+    @Test
     fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
         testScope.runTest {
             val latest by collectLastValue(underTest.subscriptions)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 0b14be1..0f9cbfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -42,14 +42,11 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.yield
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -68,7 +65,7 @@
             set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
         }
 
-    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
     private val tableLogBuffer =
@@ -113,17 +110,12 @@
             )
     }
 
-    @After fun tearDown() {}
-
     @Test
     fun filteredSubscriptions_default() =
         testScope.runTest {
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
-
-            job.cancel()
         }
 
     // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
@@ -133,12 +125,9 @@
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
-
-            job.cancel()
         }
 
     @Test
@@ -146,12 +135,9 @@
         testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
-            job.cancel()
         }
 
     @Test
@@ -160,12 +146,9 @@
             connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
-
-            job.cancel()
         }
 
     @Test
@@ -180,12 +163,9 @@
             connectionsRepository.setSubscriptions(listOf(sub1, sub2))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(sub1, sub2))
-
-            job.cancel()
         }
 
     @Test
@@ -202,13 +182,10 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the active one when the config is false
             assertThat(latest).isEqualTo(listOf(sub3))
-
-            job.cancel()
         }
 
     @Test
@@ -225,13 +202,10 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the active one when the config is false
             assertThat(latest).isEqualTo(listOf(sub4))
-
-            job.cancel()
         }
 
     @Test
@@ -248,14 +222,11 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the primary (non-opportunistic) if the config is
             // true
             assertThat(latest).isEqualTo(listOf(sub1))
-
-            job.cancel()
         }
 
     @Test
@@ -272,14 +243,11 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the primary (non-opportunistic) if the config is
             // true
             assertThat(latest).isEqualTo(listOf(sub1))
-
-            job.cancel()
         }
 
     @Test
@@ -297,12 +265,9 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(sub3))
-
-            job.cancel()
         }
 
     @Test
@@ -320,12 +285,9 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(sub1))
-
-            job.cancel()
         }
 
     @Test
@@ -446,313 +408,345 @@
         }
 
     @Test
+    fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
+        testScope.runTest {
+            val notExclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = false,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub))
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+        }
+
+    @Test
+    fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
+        testScope.runTest {
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub))
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
+        testScope.runTest {
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+            val otherSub1 =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = false,
+                    subscriptionId = 1,
+                    carrierName = "Carrier 1",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+            val otherSub2 =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = false,
+                    subscriptionId = 2,
+                    carrierName = "Carrier 2",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            connectionsRepository.setSubscriptions(
+                listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+            )
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+        }
+
+    @Test
+    fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
+        testScope.runTest {
+            // Exclusively non-terrestrial sub
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            // Opportunistic subs
+            val (sub3, sub4) =
+                createSubscriptionPair(
+                    subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+                    opportunistic = Pair(true, true),
+                    grouped = true,
+                )
+
+            // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
+            connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            // THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
+            // leaving only sub3.
+            assertThat(latest).isEqualTo(listOf(sub3))
+        }
+
+    @Test
     fun activeDataConnection_turnedOn() =
         testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
-            var latest: Boolean? = null
-            val job =
-                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activeDataConnection_turnedOff() =
         testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
-            var latest: Boolean? = null
-            val job =
-                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             CONNECTION_1.setDataEnabled(false)
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun activeDataConnection_invalidSubId() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job =
-                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
-            yield()
 
             // An invalid active subId should tell us that data is off
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_default_validated_notFailed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_notDefault_notValidated_notFailed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = false
             connectionsRepository.defaultConnectionIsValidated.value = false
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_default_notValidated_failed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = false
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_carrierMergedDefault_notValidated_failed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.hasCarrierMergedConnection.value = true
             connectionsRepository.defaultConnectionIsValidated.value = false
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/275076959. */
     @Test
     fun failedConnection_dataSwitchInSameGroup_notFailed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // WHEN there's a data change in the same subscription group
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // THEN the default connection is *not* marked as failed because of forced validation
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // WHEN the connection is invalidated without a activeSubChangedInGroupEvent
             connectionsRepository.defaultConnectionIsValidated.value = false
 
             // THEN the connection is immediately marked as failed
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun alwaysShowDataRatIcon_configHasTrue() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
 
             val config = MobileMappings.Config()
             config.alwaysShowDataRatIcon = true
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun alwaysShowDataRatIcon_configHasFalse() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
 
             val config = MobileMappings.Config()
             config.alwaysShowDataRatIcon = false
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun alwaysUseCdmaLevel_configHasTrue() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
 
             val config = MobileMappings.Config()
             config.alwaysShowCdmaRssi = true
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun alwaysUseCdmaLevel_configHasFalse() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
 
             val config = MobileMappings.Config()
             config.alwaysShowCdmaRssi = false
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isSingleCarrier_zeroSubscriptions_false() =
         testScope.runTest {
-            var latest: Boolean? = true
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(emptyList())
-            assertThat(latest).isFalse()
 
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isSingleCarrier_oneSubscription_true() =
         testScope.runTest {
-            var latest: Boolean? = false
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1))
-            assertThat(latest).isTrue()
 
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isSingleCarrier_twoSubscriptions_false() =
         testScope.runTest {
-            var latest: Boolean? = true
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
-            assertThat(latest).isFalse()
 
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isSingleCarrier_updates() =
         testScope.runTest {
-            var latest: Boolean? = false
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1))
             assertThat(latest).isTrue()
 
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = false
             connectionsRepository.hasCarrierMergedConnection.value = false
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.hasCarrierMergedConnection.value = false
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
     fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = false
             connectionsRepository.hasCarrierMergedConnection.value = true
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_updatesWhenRepoUpdates() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
             assertThat(latest).isTrue()
@@ -762,8 +756,6 @@
 
             connectionsRepository.hasCarrierMergedConnection.value = true
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     // The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
@@ -772,95 +764,79 @@
     @Test
     fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // Trigger a data change in the same subscription group that's not yet validated
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // After 1s, the force validation bit is still present, so the connection is not marked
             // as failed
             advanceTimeBy(1000)
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             // After 2s, the force validation expires so the connection updates to failed
             advanceTimeBy(1001)
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             // GIVEN the network starts validated
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // WHEN a data change happens in the same group
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
             // WHEN the validation bit is lost
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // WHEN another data change happens in the same group
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
             // THEN the forced validation bit is still used...
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             advanceTimeBy(1000)
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             // ... but expires after 2s
             advanceTimeBy(1001)
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
@@ -869,44 +845,37 @@
             // WHEN another change in same group event happens
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // THEN the forced validation remains for exactly 2 more seconds from now
 
             // 1.500s from second event
             advanceTimeBy(1500)
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             // 2.001s from the second event
             advanceTimeBy(501)
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isForceHidden_repoHasMobileHidden_true() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isForceHidden)
 
             connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isForceHidden)
 
             connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index cfa734a1..ab10bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -47,7 +47,7 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -66,7 +66,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
index 4085b1b..923b636 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -25,8 +25,9 @@
 @SysUISingleton
 class FakeAccessibilityRepository(
     override val isTouchExplorationEnabled: MutableStateFlow<Boolean>,
+    override val isEnabled: MutableStateFlow<Boolean>,
 ) : AccessibilityRepository {
-    @Inject constructor() : this(MutableStateFlow(false))
+    @Inject constructor() : this(MutableStateFlow(false), MutableStateFlow(false))
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 66c9afb..b23767e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -17,5 +17,13 @@
 package com.android.systemui.animation
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 
-val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() }
+val Kosmos.activityTransitionAnimator by
+    Kosmos.Fixture {
+        ActivityTransitionAnimator(
+            // The main thread is checked in a bunch of places inside the different transitions
+            // animators, so we have to pass the real main executor here.
+            mainExecutor = testCase.context.mainExecutor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 77cb167..5a092f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -19,7 +19,13 @@
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testCase
 
 val Kosmos.dialogTransitionAnimator by Fixture {
-    fakeDialogTransitionAnimator(interactionJankMonitor = interactionJankMonitor)
+    fakeDialogTransitionAnimator(
+        // The main thread is checked in a bunch of places inside the different transitions
+        // animators, so we have to pass the real main executor here.
+        mainExecutor = testCase.context.mainExecutor,
+        interactionJankMonitor = interactionJankMonitor,
+    )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
index 48b72d0..1709329 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
@@ -15,17 +15,20 @@
 package com.android.systemui.animation
 
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
 
 /** A [DialogTransitionAnimator] to be used in tests. */
 @JvmOverloads
 fun fakeDialogTransitionAnimator(
+    @Main mainExecutor: Executor,
     isUnlocked: Boolean = true,
     isShowingAlternateAuthOnUnlock: Boolean = false,
     isPredictiveBackQsDialogAnim: Boolean = false,
     interactionJankMonitor: InteractionJankMonitor,
 ): DialogTransitionAnimator {
     return DialogTransitionAnimator(
+        mainExecutor = mainExecutor,
         callback =
             FakeCallback(
                 isUnlocked = isUnlocked,
@@ -36,7 +39,7 @@
             object : AnimationFeatureFlags {
                 override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
             },
-        transitionAnimator = fakeTransitionAnimator(),
+        transitionAnimator = fakeTransitionAnimator(mainExecutor),
         isForTesting = true,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index bc7ec3f..d07875f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -15,10 +15,12 @@
 package com.android.systemui.animation
 
 import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
 
 /** A [TransitionAnimator] to be used in tests. */
-fun fakeTransitionAnimator(): TransitionAnimator {
-    return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+fun fakeTransitionAnimator(@Main mainExecutor: Executor): TransitionAnimator {
+    return TransitionAnimator(mainExecutor, TEST_TIMINGS, TEST_INTERPOLATORS)
 }
 
 /**
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 58b0ff8..67fa857 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
@@ -49,6 +50,7 @@
         keyguardViewController = { statusBarKeyguardViewManager },
         deviceEntryInteractor = deviceEntryInteractor,
         deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+        accessibilityInteractor = accessibilityInteractor,
         scope = testScope.backgroundScope,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 3762497..ec56327 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -33,6 +34,7 @@
             uiEventLogger,
             { interactionJankMonitor },
             mock(),
+            { keyguardTransitionInteractor },
             { shadeInteractor },
             { deviceUnlockedInteractor },
             { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
new file mode 100644
index 0000000..5c21ab6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsNightDisplayTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideNightDisplayTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 38ede44..ea02d0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -76,6 +76,16 @@
         delegate.assertFlagValid()
         delegate.programmaticCollapseShade()
     }
+
+    fun setQsFullscreen(qsFullscreen: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setQsFullscreen(qsFullscreen)
+    }
+
+    fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer)
+    }
 }
 
 /** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -103,6 +113,10 @@
 
     /** Sets the shade to half collapsed with no touch input. */
     fun programmaticCollapseShade()
+
+    fun setQsFullscreen(qsFullscreen: Boolean)
+
+    fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
 }
 
 /** Sets up shade state for tests when the scene container flag is disabled. */
@@ -146,6 +160,14 @@
         shadeRepository.setLegacyShadeExpansion(.5f)
         testScope.runCurrent()
     }
+
+    override fun setQsFullscreen(qsFullscreen: Boolean) {
+        shadeRepository.legacyQsFullscreen.value = true
+    }
+
+    override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) {
+        shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded)
+    }
 }
 
 /** Sets up shade state for tests when the scene container flag is enabled. */
@@ -183,6 +205,16 @@
         setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false)
     }
 
+    override fun setQsFullscreen(qsFullscreen: Boolean) {
+        setQsExpansion(1f)
+    }
+
+    override fun setLegacyExpandedOrAwaitingInputTransfer(
+        legacyExpandedOrAwaitingInputTransfer: Boolean
+    ) {
+        setShadeExpansion(.1f)
+    }
+
     override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
         if (lockscreenShadeExpansion == 0f) {
             setIdleScene(Scenes.Lockscreen)
diff --git a/ravenwood/scripts/convert-androidtest.py b/ravenwood/scripts/convert-androidtest.py
new file mode 100755
index 0000000..61ec54b
--- /dev/null
+++ b/ravenwood/scripts/convert-androidtest.py
@@ -0,0 +1,184 @@
+#!/usr/bin/python3
+# 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.
+
+# This script converts a legacy test class (using AndroidTestCase, TestCase or
+# InstrumentationTestCase to a modern style test class, in a best-effort manner.
+#
+# Usage:
+#  convert-androidtest.py TARGET-FILE [TARGET-FILE ...]
+#
+# Caveats:
+#   - It adds all the extra imports, even if they're not needed.
+#   - It won't sort imports.
+#   - It also always adds getContext() and getTestContext().
+#
+
+import sys
+import fileinput
+import re
+import subprocess
+
+# Print message on console
+def log(msg):
+    print(msg, file=sys.stderr)
+
+
+# Matches `extends AndroidTestCase` (or another similar base class)
+re_extends = re.compile(
+    r''' \b extends \s+ (AndroidTestCase|TestCase|InstrumentationTestCase) \s* ''',
+    re.S + re.X)
+
+
+# Look into given files and return the files that have `re_extends`.
+def find_target_files(files):
+    ret = []
+
+    for file in files:
+        try:
+            with open(file, 'r') as f:
+                data = f.read()
+
+                if re_extends.search(data):
+                    ret.append(file)
+
+        except FileNotFoundError as e:
+            log(f'Failed to open file {file}: {e}')
+
+    return ret
+
+
+def main(args):
+    files = args
+
+    # Find the files that should be processed.
+    files = find_target_files(files)
+
+    if len(files) == 0:
+        log("No target files found.")
+        return 0
+
+    # Process the files.
+    with fileinput.input(files=(files), inplace = True, backup = '.bak') as f:
+        import_seen = False
+        carry_over = ''
+        class_body_started = False
+        class_seen = False
+
+        def on_file_start():
+            nonlocal import_seen, carry_over, class_body_started, class_seen
+            import_seen = False
+            carry_over = ''
+            class_body_started = False
+            class_seen = False
+
+        for line in f:
+            if (fileinput.filelineno() == 1):
+                log(f"Processing: {fileinput.filename()}")
+                on_file_start()
+
+            line = line.rstrip('\n')
+
+            # Carry over a certain line to the next line.
+            if re.search(r'''@Override\b''', line):
+                carry_over = carry_over + line + '\n'
+                continue
+
+            if carry_over:
+                line = carry_over + line
+                carry_over = ''
+
+
+            # Remove the base class from the class definition.
+            line = re_extends.sub('', line)
+
+            # Add a @RunWith.
+            if not class_seen and re.search(r'''\b class \b''', line, re.X):
+                class_seen = True
+                print("@RunWith(AndroidJUnit4.class)")
+
+
+            # Inject extra imports.
+            if not import_seen and re.search(r'''^import\b''', line):
+                import_seen = True
+                print("""\
+import android.content.Context;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+""")
+
+            # Add @Test to the test methods.
+            if re.search(r'''^ \s* public \s* void \s* test''', line, re.X):
+                print("    @Test")
+
+            # Convert setUp/tearDown to @Before/@After.
+            if re.search(r''' ^\s+ ( \@Override \s+ ) ? (public|protected) \s+ void \s+ (setUp|tearDown) ''',
+                        line, re.X):
+                if re.search('setUp', line):
+                    print('    @Before')
+                else:
+                    print('    @After')
+
+                line = re.sub(r''' \s* \@Override \s* \n ''', '', line, 0, re.X)
+                line = re.sub(r'''protected''', 'public', line, 0, re.X)
+
+            # Remove the super setUp / tearDown call.
+            if re.search(r''' \b super \. (setUp|tearDown) \b ''', line, re.X):
+                continue
+
+            # Convert mContext to getContext().
+            line = re.sub(r'''\b mContext \b ''', 'getContext()', line, 0, re.X)
+
+            # Print the processed line.
+            print(line)
+
+            # Add getContext() / getTestContext() at the beginning of the class.
+            if not class_body_started and re.search(r'''\{''', line):
+                class_body_started = True
+                print("""\
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    private Context getTestContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+""")
+
+
+    # Run diff
+    for file in files:
+        subprocess.call(["diff", "-u", "--color=auto", f"{file}.bak", file])
+
+    log(f'{len(files)} file(s) converted.')
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index e830523..249b3cb 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -83,6 +83,7 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -172,6 +173,7 @@
     private static final String TAG = "AppWidgetServiceImpl";
 
     private static final boolean DEBUG = false;
+    private static final boolean DEBUG_NULL_PROVIDER_INFO = Build.IS_DEBUGGABLE;
 
     private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
     private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -736,7 +738,10 @@
         }
         RemoteViews views = new RemoteViews(mContext.getPackageName(),
                 R.layout.work_widget_mask_view);
-        ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo;
+        final ActivityInfo activityInfo = provider.info.providerInfo;
+        final ApplicationInfo appInfo = activityInfo != null ? activityInfo.applicationInfo : null;
+        final String packageName = appInfo != null
+                ? appInfo.packageName : provider.id.componentName.getPackageName();
         final int appUserId = provider.getUserId();
         boolean showBadge = false;
 
@@ -750,7 +755,7 @@
             } else if (provider.maskedBySuspendedPackage) {
                 showBadge = mUserManager.hasBadge(appUserId);
                 final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
-                        appInfo.packageName, appUserId);
+                        packageName, appUserId);
                 // TODO(b/281839596): don't rely on platform always meaning suspended by admin.
                 if (suspendingPackage != null
                         && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
@@ -759,11 +764,11 @@
                 } else {
                     final SuspendDialogInfo dialogInfo =
                             mPackageManagerInternal.getSuspendedDialogInfo(
-                                    appInfo.packageName, suspendingPackage, appUserId);
+                                    packageName, suspendingPackage, appUserId);
                     // onUnsuspend is null because we don't want to start any activity on
                     // unsuspending from a suspended widget.
                     onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
-                            appInfo.packageName, suspendingPackage, dialogInfo, null, null,
+                            packageName, suspendingPackage, dialogInfo, null, null,
                             appUserId);
                 }
             } else if (provider.maskedByLockedProfile) {
@@ -778,7 +783,7 @@
                 showBadge = mUserManager.hasBadge(appUserId);
             }
 
-            Icon icon = appInfo.icon != 0
+            Icon icon = (appInfo != null && appInfo.icon != 0)
                     ? Icon.createWithResource(appInfo.packageName, appInfo.icon)
                     : Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
             views.setImageViewIcon(R.id.work_widget_app_icon, icon);
@@ -2955,6 +2960,9 @@
             AppWidgetProviderInfo info = new AppWidgetProviderInfo();
             info.provider = providerId.componentName;
             info.providerInfo = ri.activityInfo;
+            if (DEBUG_NULL_PROVIDER_INFO) {
+                Objects.requireNonNull(ri.activityInfo);
+            }
             return info;
         }
         return null;
@@ -2989,6 +2997,9 @@
             AppWidgetProviderInfo info = new AppWidgetProviderInfo();
             info.provider = providerId.componentName;
             info.providerInfo = activityInfo;
+            if (DEBUG_NULL_PROVIDER_INFO) {
+                Objects.requireNonNull(activityInfo);
+            }
 
             final Resources resources;
             final long identity = Binder.clearCallingIdentity();
@@ -3564,6 +3575,9 @@
                             AppWidgetProviderInfo info = new AppWidgetProviderInfo();
                             info.provider = providerId.componentName;
                             info.providerInfo = providerInfo;
+                            if (DEBUG_NULL_PROVIDER_INFO) {
+                                Objects.requireNonNull(providerInfo);
+                            }
 
                             provider = new Provider();
                             provider.setPartialInfoLocked(info);
@@ -3580,6 +3594,9 @@
                             if (info != null) {
                                 info.provider = providerId.componentName;
                                 info.providerInfo = providerInfo;
+                                if (DEBUG_NULL_PROVIDER_INFO) {
+                                    Objects.requireNonNull(providerInfo);
+                                }
                                 provider.setInfoLocked(info);
                             }
                         }
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 9c84b12..c4b461f 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -564,6 +564,18 @@
         });
     }
 
+    /** Sets focused_autofill_id using view id */
+    public void maybeSetFocusedId(AutofillId id) {
+        maybeSetFocusedId(id.getViewId());
+    }
+
+    /** Sets focused_autofill_id as long as mEventInternal is present */
+    public void maybeSetFocusedId(int id) {
+        mEventInternal.ifPresent(event -> {
+            event.mFocusedId = id;
+        });
+    }
+
     public void logAndEndEvent() {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -608,7 +620,8 @@
                     + " mIsCredentialRequest=" + event.mIsCredentialRequest
                     + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
                     + " mViewFillableTotalCount=" + event.mViewFillableTotalCount
-                    + " mViewFillFailureCount=" + event.mViewFillFailureCount);
+                    + " mViewFillFailureCount=" + event.mViewFillFailureCount
+                    + " mFocusedId=" + event.mFocusedId);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -651,7 +664,8 @@
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
                 event.mViewFillableTotalCount,
-                event.mViewFillFailureCount);
+                event.mViewFillFailureCount,
+                event.mFocusedId);
         mEventInternal = Optional.empty();
     }
 
@@ -689,6 +703,7 @@
         boolean mWebviewRequestedCredential = false;
         int mViewFillableTotalCount = -1;
         int mViewFillFailureCount = -1;
+        int mFocusedId = -1;
 
         PresentationStatsEventInternal() {}
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8b13c4b7..03cf74f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4669,6 +4669,7 @@
                 mFieldClassificationIdSnapshot);
         mPresentationStatsEventLogger.maybeSetAvailableCount(
                 response.getDatasets(), mCurrentViewId);
+        mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 748253f..1a8c3b0 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -35,7 +35,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Debug;
 import android.os.DropBoxManager;
@@ -176,6 +175,16 @@
         }
     };
 
+    private static final BundleMerger sDropboxEntryAddedExtrasMerger;
+    static {
+        sDropboxEntryAddedExtrasMerger = new BundleMerger();
+        sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
+        sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+                BundleMerger.STRATEGY_COMPARABLE_MAX);
+        sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+                BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
+    }
+
     private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() {
         @Override
         public void addData(String tag, byte[] data, int flags) {
@@ -284,7 +293,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SEND_BROADCAST:
-                    prepareAndSendBroadcast((Intent) msg.obj, null);
+                    prepareAndSendBroadcast((Intent) msg.obj, false);
                     break;
                 case MSG_SEND_DEFERRED_BROADCAST:
                     Intent deferredIntent;
@@ -292,31 +301,42 @@
                         deferredIntent = mDeferredMap.remove((String) msg.obj);
                     }
                     if (deferredIntent != null) {
-                        prepareAndSendBroadcast(deferredIntent,
-                                createBroadcastOptions(deferredIntent));
+                        prepareAndSendBroadcast(deferredIntent, true);
                     }
                     break;
             }
         }
 
-        private void prepareAndSendBroadcast(Intent intent, Bundle options) {
+        private void prepareAndSendBroadcast(Intent intent, boolean deferrable) {
             if (!DropBoxManagerService.this.mBooted) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
             if (Flags.enableReadDropboxPermission()) {
-                BroadcastOptions unbundledOptions = (options == null)
-                        ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
-
-                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+                options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+                if (deferrable) {
+                    final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+                            + "-READ_DROPBOX_DATA";
+                    setBroadcastOptionsForDeferral(options, matchingKey);
+                }
                 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+                        Manifest.permission.READ_DROPBOX_DATA, options.toBundle());
 
-                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+                options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+                if (deferrable) {
+                    final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+                            + "-READ_LOGS";
+                    setBroadcastOptionsForDeferral(options, matchingKey);
+                }
                 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+                        Manifest.permission.READ_LOGS, options.toBundle());
             } else {
+                if (deferrable) {
+                    final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
+                    setBroadcastOptionsForDeferral(options, matchingKey);
+                }
                 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        android.Manifest.permission.READ_LOGS, options);
+                        android.Manifest.permission.READ_LOGS, options.toBundle());
             }
         }
 
@@ -328,21 +348,12 @@
             return dropboxIntent;
         }
 
-        private Bundle createBroadcastOptions(Intent intent) {
-            final BundleMerger extrasMerger = new BundleMerger();
-            extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
-            extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
-                    BundleMerger.STRATEGY_COMPARABLE_MAX);
-            extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
-                    BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
-
-            return BroadcastOptions.makeBasic()
-                    .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
+        private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) {
+            options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
                     .setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED,
-                            intent.getStringExtra(DropBoxManager.EXTRA_TAG))
-                    .setDeliveryGroupExtrasMerger(extrasMerger)
-                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
-                    .toBundle();
+                            matchingKey)
+                    .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger)
+                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         }
 
         /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d776700..bef5c612 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1168,9 +1168,7 @@
     }
 
     private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
-        @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
-                r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
-                                                      : REASON_DENIED;
+        @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = r.getFgsAllowStart();
         if (Flags.fgsBootCompleted()
                 && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
                 && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cc6ae5c..8647750 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3277,7 +3277,13 @@
         }
 
         final int curSchedGroup = state.getCurrentSchedulingGroup();
-        if (state.getSetSchedGroup() != curSchedGroup) {
+        if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+                && ActivityManager.isProcStateBackground(state.getCurProcState())
+                && !state.hasStartedServices()) {
+            app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
+                    ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
+            success = false;
+        } else if (state.getSetSchedGroup() != curSchedGroup) {
             int oldSchedGroup = state.getSetSchedGroup();
             state.setSetSchedGroup(curSchedGroup);
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
@@ -3285,74 +3291,66 @@
                         + " to " + curSchedGroup + ": " + state.getAdjType();
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
             }
-            if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
-                    && ActivityManager.isProcStateBackground(state.getSetProcState())
-                    && !state.hasStartedServices()) {
-                app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
-                        ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
-                success = false;
-            } else {
-                int processGroup;
-                switch (curSchedGroup) {
-                    case SCHED_GROUP_BACKGROUND:
-                        processGroup = THREAD_GROUP_BACKGROUND;
-                        break;
-                    case SCHED_GROUP_TOP_APP:
-                    case SCHED_GROUP_TOP_APP_BOUND:
-                        processGroup = THREAD_GROUP_TOP_APP;
-                        break;
-                    case SCHED_GROUP_RESTRICTED:
-                        processGroup = THREAD_GROUP_RESTRICTED;
-                        break;
-                    default:
-                        processGroup = THREAD_GROUP_DEFAULT;
-                        break;
-                }
-                mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
-                        0 /* unused */, app.getPid(), processGroup, app.processName));
-                try {
-                    final int renderThreadTid = app.getRenderThreadTid();
-                    if (curSchedGroup == SCHED_GROUP_TOP_APP) {
-                        // do nothing if we already switched to RT
-                        if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
-                            app.getWindowProcessController().onTopProcChanged();
-                            if (app.useFifoUiScheduling()) {
-                                // Switch UI pipeline for app to SCHED_FIFO
-                                state.setSavedPriority(Process.getThreadPriority(app.getPid()));
-                                ActivityManagerService.setFifoPriority(app, true /* enable */);
-                            } else {
-                                // Boost priority for top app UI and render threads
-                                setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
-                                if (renderThreadTid != 0) {
-                                    try {
-                                        setThreadPriority(renderThreadTid,
-                                                THREAD_PRIORITY_TOP_APP_BOOST);
-                                    } catch (IllegalArgumentException e) {
-                                        // thread died, ignore
-                                    }
+            int processGroup;
+            switch (curSchedGroup) {
+                case SCHED_GROUP_BACKGROUND:
+                    processGroup = THREAD_GROUP_BACKGROUND;
+                    break;
+                case SCHED_GROUP_TOP_APP:
+                case SCHED_GROUP_TOP_APP_BOUND:
+                    processGroup = THREAD_GROUP_TOP_APP;
+                    break;
+                case SCHED_GROUP_RESTRICTED:
+                    processGroup = THREAD_GROUP_RESTRICTED;
+                    break;
+                default:
+                    processGroup = THREAD_GROUP_DEFAULT;
+                    break;
+            }
+            mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
+                    0 /* unused */, app.getPid(), processGroup, app.processName));
+            try {
+                final int renderThreadTid = app.getRenderThreadTid();
+                if (curSchedGroup == SCHED_GROUP_TOP_APP) {
+                    // do nothing if we already switched to RT
+                    if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
+                        app.getWindowProcessController().onTopProcChanged();
+                        if (app.useFifoUiScheduling()) {
+                            // Switch UI pipeline for app to SCHED_FIFO
+                            state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+                            ActivityManagerService.setFifoPriority(app, true /* enable */);
+                        } else {
+                            // Boost priority for top app UI and render threads
+                            setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+                            if (renderThreadTid != 0) {
+                                try {
+                                    setThreadPriority(renderThreadTid,
+                                            THREAD_PRIORITY_TOP_APP_BOOST);
+                                } catch (IllegalArgumentException e) {
+                                    // thread died, ignore
                                 }
                             }
                         }
-                    } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
-                            && curSchedGroup != SCHED_GROUP_TOP_APP) {
-                        app.getWindowProcessController().onTopProcChanged();
-                        if (app.useFifoUiScheduling()) {
-                            // Reset UI pipeline to SCHED_OTHER
-                            ActivityManagerService.setFifoPriority(app, false /* enable */);
-                            setThreadPriority(app.getPid(), state.getSavedPriority());
-                        } else {
-                            // Reset priority for top app UI and render threads
-                            setThreadPriority(app.getPid(), 0);
-                        }
+                    }
+                } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
+                        && curSchedGroup != SCHED_GROUP_TOP_APP) {
+                    app.getWindowProcessController().onTopProcChanged();
+                    if (app.useFifoUiScheduling()) {
+                        // Reset UI pipeline to SCHED_OTHER
+                        ActivityManagerService.setFifoPriority(app, false /* enable */);
+                        setThreadPriority(app.getPid(), state.getSavedPriority());
+                    } else {
+                        // Reset priority for top app UI and render threads
+                        setThreadPriority(app.getPid(), 0);
+                    }
 
-                        if (renderThreadTid != 0) {
-                            setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
-                        }
+                    if (renderThreadTid != 0) {
+                        setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
                     }
-                } catch (Exception e) {
-                    if (DEBUG_ALL) {
-                        Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
-                    }
+                }
+            } catch (Exception e) {
+                if (DEBUG_ALL) {
+                    Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 85eb044..1db3483 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2912,10 +2912,12 @@
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final int proxiedUid = attributionSource.getNextUid();
         final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+        final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+        final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
@@ -2952,7 +2954,8 @@
 
             final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
                     resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
-                    Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+                    Process.INVALID_UID, null, null,
+                    Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
                     "proxy " + message, shouldCollectMessage);
             if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
@@ -2970,9 +2973,9 @@
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
         return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
-                proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
-                proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
+                proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
+                message, shouldCollectMessage);
     }
 
     @Override
@@ -3023,14 +3026,14 @@
         }
         return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
                 virtualDeviceId, Process.INVALID_UID, null, null,
-                AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
+                message, shouldCollectMessage);
     }
 
     private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
-            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
+            @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
             boolean shouldCollectMessage) {
         PackageVerificationResult pvr;
         try {
@@ -3161,8 +3164,9 @@
             }
             scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                     virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
+
             attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState.getState(), flags);
+                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
 
             if (shouldCollectAsyncNotedOp) {
                 collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3528,9 +3532,9 @@
         }
 
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
-                virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                attributionFlags, attributionChainId);
+                virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT,
+                OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage, attributionFlags, attributionChainId);
     }
 
     /** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3568,18 +3572,32 @@
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final int proxiedUid = attributionSource.getNextUid();
         final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+        final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+        final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
         if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
-            Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
-                    + proxyVirtualDeviceId + " is invalid");
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
-                    proxiedPackageName);
+            Slog.w(
+                    TAG,
+                    "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId "
+                            + proxyVirtualDeviceId
+                            + " is invalid");
+            return new SyncNotedAppOp(
+                    AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
+        }
+        if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) {
+            Slog.w(
+                    TAG,
+                    "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId "
+                            + proxiedVirtualDeviceId
+                            + " is invalid");
+            return new SyncNotedAppOp(
+                    AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
         }
         if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
                 || !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
@@ -3621,7 +3639,7 @@
             // Test if the proxied operation will succeed before starting the proxy operation
             final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
-                    proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+                    proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
                     startIfModeDefault);
 
             if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
@@ -3633,7 +3651,7 @@
 
             final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
-                    Process.INVALID_UID, null, null, proxyFlags,
+                    Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags,
                     startIfModeDefault, !isProxyTrusted, "proxy " + message,
                     shouldCollectMessage, proxyAttributionFlags, attributionChainId);
             if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
@@ -3642,9 +3660,10 @@
         }
 
         return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
-                proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+                proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+                proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags,
+                attributionChainId);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3654,9 +3673,10 @@
     private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
             @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
             int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
-            @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
-            @Nullable String message, boolean shouldCollectMessage,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
+            int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault,
+            boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3751,13 +3771,13 @@
                     + " flags: " + AppOpsManager.flagsToString(flags));
             try {
                 if (isRestricted) {
-                    attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                            proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
-                            attributionFlags, attributionChainId);
+                    attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+                            proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+                            uidState.getState(), flags, attributionFlags, attributionChainId);
                 } else {
-                    attributedOp.started(clientId, proxyUid, proxyPackageName,
-                            proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
-                            attributionFlags, attributionChainId);
+                    attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+                            proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+                            uidState.getState(), flags, attributionFlags, attributionChainId);
                     startType = START_TYPE_STARTED;
                 }
             } catch (RemoteException e) {
@@ -4946,7 +4966,7 @@
 
         if (accessTime > 0) {
             attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
-                    proxyAttributionTag, uidState, opFlags);
+                    proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags);
         }
         if (rejectTime > 0) {
             attributedOp.rejected(rejectTime, uidState, opFlags);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2285826..2760ccf 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,7 +24,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.companion.virtual.VirtualDeviceManager;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -95,16 +94,17 @@
      *
      * @param proxyUid            The uid of the proxy
      * @param proxyPackageName    The package name of the proxy
-     * @param proxyAttributionTag the attributionTag in the proxies package
+     * @param proxyAttributionTag The attributionTag in the proxies package
+     * @param proxyDeviceId       The device Id of the proxy
      * @param uidState            UID state of the app noteOp/startOp was called for
      * @param flags               OpFlags of the call
      */
     public void accessed(int proxyUid, @Nullable String proxyPackageName,
-            @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
-            @AppOpsManager.OpFlags int flags) {
+            @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
         long accessTime = System.currentTimeMillis();
-        accessed(accessTime, -1, proxyUid, proxyPackageName,
-                proxyAttributionTag, uidState, flags);
+        accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
+                uidState, flags);
 
         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                 parent.packageName, tag, uidState, flags, accessTime,
@@ -118,14 +118,16 @@
      * @param duration            The duration of the event
      * @param proxyUid            The uid of the proxy
      * @param proxyPackageName    The package name of the proxy
-     * @param proxyAttributionTag the attributionTag in the proxies package
+     * @param proxyAttributionTag The attributionTag in the proxies package
+     * @param proxyDeviceId       The device Id of the proxy
      * @param uidState            UID state of the app noteOp/startOp was called for
      * @param flags               OpFlags of the call
      */
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     public void accessed(long noteTime, long duration, int proxyUid,
             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags) {
         long key = makeKey(uidState, flags);
 
         if (mAccessEvents == null) {
@@ -135,7 +137,7 @@
         AppOpsManager.OpEventProxyInfo proxyInfo = null;
         if (proxyUid != Process.INVALID_UID) {
             proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                            proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+                    proxyAttributionTag, proxyDeviceId);
         }
 
         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -189,35 +191,36 @@
      * Update state when start was called
      *
      * @param clientId            Id of the startOp caller
+     * @param virtualDeviceId     The virtual device id of the startOp caller
      * @param proxyUid            The UID of the proxy app
      * @param proxyPackageName    The package name of the proxy app
      * @param proxyAttributionTag The attribution tag of the proxy app
+     * @param proxyDeviceId       The device id of the proxy app
      * @param uidState            UID state of the app startOp is called for
      * @param flags               The proxy flags
      * @param attributionFlags    The attribution flags associated with this operation.
-     * @param attributionChainId  The if of the attribution chain this operations is a part of.
+     * @param attributionChainId  The if of the attribution chain this operations is a part of
      */
-    public void started(@NonNull IBinder clientId, int proxyUid,
+    public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
             @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
             int attributionChainId) throws RemoteException {
-        startedOrPaused(clientId, proxyUid, proxyPackageName,
-                proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
-                /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
-                attributionChainId);
+        startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+                proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+                true);
     }
 
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
-    private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+    private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
-            @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
-            boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
-            int attributionChainId) throws RemoteException {
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted)
+            throws RemoteException {
         if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
             mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                    parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+                    parent.packageName, tag, virtualDeviceId, true, attributionFlags,
                     attributionChainId);
         }
 
@@ -233,9 +236,9 @@
         InProgressStartOpEvent event = events.get(clientId);
         if (event == null) {
             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
-                    SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
+                    SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId,
                     PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
-                    proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+                    proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags,
                     attributionFlags, attributionChainId);
             events.put(clientId, event);
         } else {
@@ -366,15 +369,14 @@
     /**
      * Create an event that will be started, if the op is unpaused.
      */
-    public void createPaused(@NonNull IBinder clientId, int proxyUid,
-            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
-            @AppOpsManager.OpFlags int flags,
-            @AppOpsManager.AttributionFlags int attributionFlags,
+    public void createPaused(@NonNull IBinder clientId, int virtualDeviceId,
+            int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
             int attributionChainId) throws RemoteException {
-        startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                proxyVirtualDeviceId, uidState, flags, false, false,
-                attributionFlags, attributionChainId);
+        startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+                proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+                false);
     }
 
     /**
@@ -496,16 +498,16 @@
                     // Call started() to add a new start event object and then add the
                     // previously removed unfinished start counts back
                     if (proxy != null) {
-                        startedOrPaused(event.getClientId(), proxy.getUid(),
-                                proxy.getPackageName(), proxy.getAttributionTag(),
-                                event.getVirtualDeviceId(), newState, event.getFlags(),
-                                true, isRunning,
-                                event.getAttributionFlags(), event.getAttributionChainId());
+                        startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+                                proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(),
+                                proxy.getDeviceId(), newState, event.getFlags(),
+                                event.getAttributionFlags(), event.getAttributionChainId(), true,
+                                isRunning);
                     } else {
-                        startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
-                                event.getVirtualDeviceId(), newState, event.getFlags(), true,
-                                isRunning, event.getAttributionFlags(),
-                                event.getAttributionChainId());
+                        startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+                                Process.INVALID_UID, null, null, null,
+                                newState, event.getFlags(), event.getAttributionFlags(),
+                                event.getAttributionChainId(), true, isRunning);
                     }
 
                     events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -847,7 +849,8 @@
         InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
                 @Nullable String attributionTag, int virtualDeviceId,  @NonNull Runnable onDeath,
                 int proxyUid, @Nullable String proxyPackageName,
-                @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+                @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+                @AppOpsManager.UidState int uidState,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
                 int attributionFlags, int attributionChainId) throws RemoteException {
 
@@ -856,7 +859,7 @@
             AppOpsManager.OpEventProxyInfo proxyInfo = null;
             if (proxyUid != Process.INVALID_UID) {
                 proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                        proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+                        proxyAttributionTag, proxyDeviceId);
             }
 
             if (recycled != null) {
@@ -880,7 +883,8 @@
             super(maxUnusedPooledObjects);
         }
 
-        AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+        AppOpsManager.OpEventProxyInfo acquire(
+                @IntRange(from = 0) int uid,
                 @Nullable String packageName,
                 @Nullable String attributionTag,
                 @Nullable String deviceId) {
@@ -890,7 +894,7 @@
                 return recycled;
             }
 
-            return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+            return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
index 3e8acee..7cf2d30 100644
--- a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
+++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
@@ -16,6 +16,7 @@
 package com.android.server.biometrics;
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
@@ -63,7 +64,7 @@
             intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH);
             intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS);
         }
-        context.registerReceiver(this, intentFilter);
+        context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED);
     }
 
     @Override
@@ -84,7 +85,8 @@
     }
 
     private void launchBiometricEnrollActivity(Context context, String action) {
-        context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS));
+        context.sendBroadcast(
+                new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
         final Intent intent = new Intent(action);
         intent.setPackage(SETTINGS_PACKAGE);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d7a7dd4..70a1014 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1442,6 +1442,9 @@
 
         // If there's an offload session, we need to set the initial doze brightness before
         // the offload session starts controlling the brightness.
+        // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
+        // will be selected again, meaning that no new brightness will be sent to the hardware and
+        // the display will stay at the brightness level set by the offload session.
         if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
                 && Display.isDozeState(state) && mDisplayOffloadSession != null) {
             if (mAutomaticBrightnessController != null
@@ -1459,6 +1462,15 @@
             if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+
+                if (mAutomaticBrightnessController != null
+                        && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+                    // Keep the brightness in the setting so that we can use it after the screen
+                    // turns on, until a lux sample becomes available. We don't do this when
+                    // auto-brightness is disabled - in that situation we still want to use
+                    // the last brightness from when the screen was on.
+                    updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 2703a2c0..7e18d84 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -158,9 +158,11 @@
                 mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() {
                 @Override
                 public void onSendCompleted(int error) {
-                    if (error != SendMessageResult.SUCCESS) {
+                    // Disable System Audio Mode, if the AVR doesn't acknowledge
+                    // a <User Control Pressed> message.
+                    if (error == SendMessageResult.NACK) {
                         HdmiLogger.debug(
-                            "AVR did not respond to <User Control Pressed>");
+                            "AVR did not acknowledge <User Control Pressed>");
                         localDevice().mService.setSystemAudioActivated(false);
                     }
                 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index cbd309e..308aed6 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -156,7 +156,6 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
-    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -1324,11 +1323,6 @@
                 properties -> properties.pointerIconVisible = visible);
     }
 
-    private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
-        mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
-                args.mPointerDisplayId, args.mXPosition, args.mYPosition);
-    }
-
     private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
         mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
     }
@@ -1612,18 +1606,6 @@
 
     // Binder call
     @Override
-    public void setPointerIconType(int iconType) {
-        // TODO(b/311416205): Remove.
-    }
-
-    // Binder call
-    @Override
-    public void setCustomPointerIcon(PointerIcon icon) {
-        // TODO(b/311416205): Remove.
-    }
-
-    // Binder call
-    @Override
     public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
             IBinder inputToken) {
         Objects.requireNonNull(icon);
@@ -2703,9 +2685,7 @@
     @SuppressWarnings("unused")
     @VisibleForTesting
     void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
-        mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
-                new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
-                        yPosition)).sendToTarget();
+        // TODO(b/311416205): Remove.
     }
 
     @Override
@@ -2860,14 +2840,6 @@
          */
         @Nullable
         SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
-
-        /**
-         * Notify WindowManagerService when the display of the mouse pointer changes.
-         * @param displayId The display on which the mouse pointer is shown.
-         * @param x The x coordinate of the mouse pointer.
-         * @param y The y coordinate of the mouse pointer.
-         */
-        void notifyPointerDisplayIdChanged(int displayId, float x, float y);
     }
 
     /**
@@ -2911,9 +2883,6 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
-                case MSG_POINTER_DISPLAY_ID_CHANGED:
-                    handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
-                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 7956e03..79f1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -330,14 +330,10 @@
         mHandwritingSurface.startIntercepting(imePid, imeUid);
 
         // Unset the pointer icon for the stylus in case the app had set it.
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
-                    PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
-                    downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
-                    mHandwritingSurface.getInputChannel().getToken());
-        } else {
-            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
-        }
+        Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+                PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+                downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+                mHandwritingSurface.getInputChannel().getToken());
 
         return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
                 mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0fde760..25e2e3a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2250,7 +2250,9 @@
         // Check if the input method is changing.
         // We expect the caller has already verified that the client is allowed to access this
         // display ID.
-        if (isSelectedMethodBoundLocked()) {
+        final String curId = bindingController.getCurId();
+        if (curId != null && curId.equals(bindingController.getSelectedMethodId())
+                && mDisplayIdToShowIme == mCurTokenDisplayId) {
             if (cs.mCurSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
@@ -2369,13 +2371,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean isSelectedMethodBoundLocked() {
-        String curId = getCurIdLocked();
-        return curId != null && curId.equals(getSelectedMethodIdLocked())
-                && mDisplayIdToShowIme == mCurTokenDisplayId;
-    }
-
-    @GuardedBy("ImfLock.class")
     private void prepareClientSwitchLocked(ClientState cs) {
         // If the client is changing, we need to switch over to the new
         // one.
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 0049213..d932bd4 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -32,6 +32,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.HandlerThread;
 import android.os.LocaleList;
@@ -101,6 +102,11 @@
     // the application setting the app-locale itself.
     private final SharedPreferences mDelegateAppLocalePackages;
     private final BroadcastReceiver mUserMonitor;
+    // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving
+    // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data
+    // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the
+    // app is installed.
+    private final Set<String> mPkgsToRestore;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManager packageManager, HandlerThread broadcastHandlerThread) {
@@ -119,6 +125,7 @@
         mStagedData = stagedData;
         mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
                 : createPersistedInfo();
+        mPkgsToRestore = new ArraySet<>();
 
         mUserMonitor = new UserMonitor();
         IntentFilter filter = new IntentFilter();
@@ -251,6 +258,9 @@
                 LocalesInfo localesInfo = pkgStates.get(pkgName);
                 // Check if the application is already installed for the concerned user.
                 if (isPackageInstalledForUser(pkgName, userId)) {
+                    if (mPkgsToRestore != null) {
+                        mPkgsToRestore.remove(pkgName);
+                    }
                     // Don't apply the restore if the locales have already been set for the app.
                     checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
                 } else {
@@ -279,23 +289,18 @@
 
     /**
      * <p><b>Note:</b> This is invoked by service's common monitor
-     * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is
+     * {@link LocaleManagerServicePackageMonitor#onPackageAddedWithExtras} when a new package is
      * added on device.
      */
-    void onPackageAdded(String packageName, int uid) {
-        try {
-            synchronized (mStagedDataLock) {
-                cleanStagedDataForOldEntriesLocked();
-
-                int userId = UserHandle.getUserId(uid);
-                if (mStagedData.contains(userId)) {
-                    // Perform lazy restore only if the staged data exists.
-                    doLazyRestoreLocked(packageName, userId);
-                }
+    void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+        boolean archived = false;
+        if (extras != null) {
+            archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
+            if (archived && mPkgsToRestore != null) {
+                mPkgsToRestore.add(packageName);
             }
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception in onPackageAdded.", e);
         }
+        checkStageDataAndApplyRestore(packageName, uid);
     }
 
     /**
@@ -305,6 +310,10 @@
      */
     void onPackageUpdateFinished(String packageName, int uid) {
         int userId = UserHandle.getUserId(uid);
+        if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) {
+            mPkgsToRestore.remove(packageName);
+            checkStageDataAndApplyRestore(packageName, uid);
+        }
         cleanApplicationLocalesIfNeeded(packageName, userId);
     }
 
@@ -338,6 +347,25 @@
         }
     }
 
+    private void checkStageDataAndApplyRestore(String packageName, int uid) {
+        try {
+            synchronized (mStagedDataLock) {
+                cleanStagedDataForOldEntriesLocked();
+
+                int userId = UserHandle.getUserId(uid);
+                if (mStagedData.contains(userId)) {
+                    if (mPkgsToRestore != null) {
+                        mPkgsToRestore.remove(packageName);
+                    }
+                    // Perform lazy restore only if the staged data exists.
+                    doLazyRestoreLocked(packageName, userId);
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception in onPackageAdded.", e);
+        }
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         PackageInfo pkgInfo = null;
         try {
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index ecd3614..e0a050f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -17,6 +17,7 @@
 package com.android.server.locales;
 
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import com.android.internal.content.PackageMonitor;
@@ -48,8 +49,8 @@
     }
 
     @Override
-    public void onPackageAdded(String packageName, int uid) {
-        mBackupHelper.onPackageAdded(packageName, uid);
+    public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+        mBackupHelper.onPackageAddedWithExtras(packageName, uid, extras);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index a2f1942..17f8abe 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -335,6 +335,10 @@
 
             if (Flags.reliableMessageDuplicateDetectionService()
                 && didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) {
+                Log.i(TAG, "[TEST MODE] Duplicating message ("
+                        + NUM_MESSAGES_TO_DUPLICATE
+                        + " sends) with message sequence number: "
+                        + message.getMessageSequenceNumber());
                 for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
                     handleClientMessageCallback(contextHubId, hostEndpointId,
                             message, nanoappPermissions, messagePermissions);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 42ec1c3..61054a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11271,6 +11271,9 @@
 
             // Lifetime extended notifications don't need to alert on state change.
             record.setPostSilently(true);
+            // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again.
+            record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+
             mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
                     record, isAppForeground,
                     mPostNotificationTrackerFactory.newTracker(null)));
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1309e44..41d6288 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2139,10 +2139,17 @@
                     continue;
                 }
 
+                ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
+                        originalComponentName);
+                if (unflattenOriginalComponentName == null) {
+                    Slog.d(TAG, "Incorrect component name from the attributes");
+                    continue;
+                }
+
                 activityInfos.add(
                         new ArchiveState.ArchiveActivityInfo(
                                 title,
-                                ComponentName.unflattenFromString(originalComponentName),
+                                unflattenOriginalComponentName,
                                 iconPath,
                                 monochromeIconPath));
             }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4e02470..483d308 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -859,7 +859,6 @@
                 break;
 
             case android.provider.Settings.System.SCREEN_BRIGHTNESS:
-            case android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT:
             case android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE:
                 if (callingUid == Process.SYSTEM_UID) {
                     return false;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 25885ed..e8faff6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -96,6 +96,7 @@
     interface TransactionReadyListener {
         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
         default void onTransactionCommitTimeout() {}
+        default void onReadyTimeout() {}
     }
 
     /**
@@ -410,6 +411,7 @@
             if (allFinished && !mReady) {
                 Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
                         + "this, please file a bug.");
+                mListener.onReadyTimeout();
             }
             finishNow();
             removeFromDependencies(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4147249..c9a5e71 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -550,15 +550,6 @@
     // TODO(multi-display): remove some of the usages.
     boolean isDefaultDisplay;
 
-    /** Detect user tapping outside of current focused task bounds .*/
-    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
-    @VisibleForTesting
-    final TaskTapPointerEventListener mTapDetector;
-
-    /** Detect user tapping outside of current focused root task bounds .*/
-    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
-    private Region mTouchExcludeRegion = new Region();
-
     /** Save allocating when calculating rects */
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRect2 = new Rect();
@@ -571,10 +562,6 @@
 
     final PinnedTaskController mPinnedTaskController;
 
-    final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
-    /** A collection of windows that provide tap exclude regions inside of them. */
-    final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>();
-
     private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
 
     private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
@@ -1193,18 +1180,6 @@
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
         mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
 
-        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
-            mTapDetector = null;
-        } else {
-            // Tap Listeners are supported for:
-            // 1. All physical displays (multi-display).
-            // 2. VirtualDisplays on VR, AA (and everything else).
-            mTapDetector = new TaskTapPointerEventListener(mWmService, this);
-            registerPointerEventListener(mTapDetector);
-        }
-        if (mWmService.mMousePositionTracker != null) {
-            registerPointerEventListener(mWmService.mMousePositionTracker);
-        }
         if (mWmService.mAtmService.getRecentTasks() != null) {
             registerPointerEventListener(
                     mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -3304,117 +3279,6 @@
                 mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
     }
 
-    void updateTouchExcludeRegion() {
-        if (mTapDetector == null) {
-            // The touch exclude region is used to detect the region outside of the focused task
-            // so that the tap detector can detect outside touches. Don't calculate the exclude
-            // region when the tap detector is disabled.
-            return;
-        }
-        final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
-        if (focusedTask == null) {
-            mTouchExcludeRegion.setEmpty();
-        } else {
-            mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-            final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-            mTmpRect.setEmpty();
-            mTmpRect2.setEmpty();
-
-            forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); });
-
-            // If we removed the focused task above, add it back and only leave its
-            // outside touch area in the exclusion. TapDetector is not interested in
-            // any touch inside the focused task itself.
-            if (!mTmpRect2.isEmpty()) {
-                mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
-            }
-        }
-        if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) {
-            // If the input method is visible and the user is typing, we don't want these touch
-            // events to be intercepted and used to change focus. This would likely cause a
-            // disappearance of the input method.
-            mInputMethodWindow.getTouchableRegion(mTmpRegion);
-            mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
-        }
-        for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mTapExcludedWindows.get(i);
-            if (!win.isVisible()) {
-                continue;
-            }
-            win.getTouchableRegion(mTmpRegion);
-            mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
-        }
-        amendWindowTapExcludeRegion(mTouchExcludeRegion);
-        mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
-    }
-
-    private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
-        if (mTapDetector == null) {
-            // The touch exclude region is used to detect the region outside of the focused task
-            // so that the tap detector can detect outside touches. Don't calculate the exclude
-            // region when the tap detector is disabled.
-        }
-        final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
-
-        if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
-            return;
-        }
-
-        // Exclusion region is the region that TapDetector doesn't care about.
-        // Here we want to remove all non-focused tasks from the exclusion region.
-        // We also remove the outside touch area for resizing for all freeform
-        // tasks (including the focused).
-        // We save the focused task region once we find it, and add it back at the end.
-        // If the task is root home task and it is resizable and visible (top of its root task),
-        // we want to exclude the root docked task from touch so we need the entire screen area
-        // and not just a small portion which the root home task currently is resized to.
-        if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) {
-            task.getDisplayArea().getBounds(mTmpRect);
-        } else {
-            task.getDimBounds(mTmpRect);
-        }
-
-        if (task == focusedTask) {
-            // Add the focused task rect back into the exclude region once we are done
-            // processing root tasks.
-            // NOTE: this *looks* like a no-op, but this usage of mTmpRect2 is expected by
-            //       updateTouchExcludeRegion.
-            mTmpRect2.set(mTmpRect);
-        }
-
-        final boolean isFreeformed = task.inFreeformWindowingMode();
-        if (task != focusedTask || isFreeformed) {
-            if (isFreeformed) {
-                // If the task is freeformed, enlarge the area to account for outside
-                // touch area for resize.
-                mTmpRect.inset(-delta, -delta);
-                // Intersect with display content frame. If we have system decor (status bar/
-                // navigation bar), we want to exclude that from the tap detection.
-                // Otherwise, if the app is partially placed under some system button (eg.
-                // Recents, Home), pressing that button would cause a full series of
-                // unwanted transfer focus/resume/pause, before we could go home.
-                mTmpRect.inset(getInsetsStateController().getRawInsetsState().calculateInsets(
-                        mTmpRect, systemBars() | ime(), false /* ignoreVisibility */));
-            }
-            mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
-        }
-    }
-
-    /**
-     * Union the region with all the tap exclude region provided by windows on this display.
-     *
-     * @param inOutRegion The region to be amended.
-     */
-    private void amendWindowTapExcludeRegion(Region inOutRegion) {
-        final Region region = Region.obtain();
-        for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
-            win.getTapExcludeRegion(region);
-            inOutRegion.op(region, Op.UNION);
-        }
-        region.recycle();
-    }
-
     @Override
     void switchUser(int userId) {
         super.switchUser(userId);
@@ -3771,7 +3635,6 @@
         pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
         pw.print(subPrefix + "deferred=" + mDeferredRemoval
                 + " mLayoutNeeded=" + mLayoutNeeded);
-        pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
 
         pw.println();
         super.dump(pw, prefix, dumpAll);
@@ -4120,7 +3983,6 @@
         }
 
         getInputMonitor().setFocusedAppLw(newFocus);
-        updateTouchExcludeRegion();
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 8116f68..30f2d0d 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -21,13 +21,11 @@
 import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
 import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
 
-import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.content.ClipData;
 import android.content.Context;
 import android.hardware.input.InputManagerGlobal;
@@ -266,16 +264,12 @@
 
                 final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
                 mDragState.broadcastDragStartedLocked(touchX, touchY);
-                if (enablePointerChoreographer()) {
-                    if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
-                        InputManagerGlobal.getInstance().setPointerIcon(
-                                PointerIcon.getSystemIcon(
-                                        mService.mContext, PointerIcon.TYPE_GRABBING),
-                                mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
-                                touchPointerId, mDragState.getInputToken());
-                    }
-                } else {
-                    mDragState.overridePointerIconLocked(touchSource);
+                if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+                    InputManagerGlobal.getInstance().setPointerIcon(
+                            PointerIcon.getSystemIcon(
+                                    mService.mContext, PointerIcon.TYPE_GRABBING),
+                            mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
+                            touchPointerId, mDragState.getInputToken());
                 }
                 // remember the thumb offsets for later
                 mDragState.mThumbOffsetX = thumbCenterX;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 5ed343a..72ae64c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -45,7 +45,6 @@
 import android.content.ClipDescription;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.input.InputManagerGlobal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -58,9 +57,7 @@
 import android.view.DragEvent;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
-import android.view.InputDevice;
 import android.view.InputWindowHandle;
-import android.view.PointerIcon;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
@@ -110,7 +107,6 @@
     boolean mCrossProfileCopyAllowed;
     ClipData mData;
     ClipDescription mDataDescription;
-    int mTouchSource;
     boolean mDragResult;
     boolean mRelinquishDragSurfaceToDropTarget;
     float mAnimatedScale = 1.0f;
@@ -263,12 +259,6 @@
             Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_ENDED");
         }
 
-        // Take the cursor back if it has been changed.
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
-            mTouchSource = 0;
-        }
-
         // Clear the internal variables.
         if (mInputSurface != null) {
             mTransaction.remove(mInputSurface).apply();
@@ -762,18 +752,6 @@
         return animator;
     }
 
-    private boolean isFromSource(int source) {
-        return (mTouchSource & source) == source;
-    }
-
-    void overridePointerIconLocked(int touchSource) {
-        mTouchSource = touchSource;
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            // TODO(b/293587049): Pointer Icon Refactor: Set the pointer icon from the drag window.
-            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
-        }
-    }
-
     private class AnimationListener
             implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
         @Override
diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java
index 119fafd..ae6e724 100644
--- a/services/core/java/com/android/server/wm/InputConfigAdapter.java
+++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java
@@ -20,8 +20,6 @@
 import android.view.InputWindowHandle.InputConfigFlags;
 import android.view.WindowManager.LayoutParams;
 
-import java.util.List;
-
 /**
  * A helper to determine the {@link InputConfigFlags} that control the behavior of an input window
  * from several WM attributes.
@@ -47,7 +45,7 @@
      * input configurations that can be mapped directly from a corresponding LayoutParams input
      * feature.
      */
-    private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of(
+    private static final FlagMapping[] INPUT_FEATURE_TO_CONFIG_MAP = {
             new FlagMapping(
                     LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL,
                     InputConfig.NO_INPUT_CHANNEL, false /* inverted */),
@@ -59,7 +57,8 @@
                     InputConfig.SPY, false /* inverted */),
             new FlagMapping(
                     LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
-                    InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */));
+                    InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */)
+    };
 
     @InputConfigFlags
     private static final int INPUT_FEATURE_TO_CONFIG_MASK =
@@ -72,7 +71,7 @@
      * NOTE: The layout params flag {@link LayoutParams#FLAG_NOT_FOCUSABLE} is not handled by this
      * adapter, and must be handled explicitly.
      */
-    private static final List<FlagMapping> LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = List.of(
+    private static final FlagMapping[] LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = {
             new FlagMapping(
                     LayoutParams.FLAG_NOT_TOUCHABLE,
                     InputConfig.NOT_TOUCHABLE, false /* inverted */),
@@ -84,7 +83,8 @@
                     InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */),
             new FlagMapping(
                     LayoutParams.FLAG_SLIPPERY,
-                    InputConfig.SLIPPERY, false /* inverted */));
+                    InputConfig.SLIPPERY, false /* inverted */)
+    };
 
     @InputConfigFlags
     private static final int LAYOUT_PARAM_FLAG_TO_CONFIG_MASK =
@@ -119,7 +119,7 @@
     }
 
     @InputConfigFlags
-    private static int applyMapping(int flags, List<FlagMapping> flagToConfigMap) {
+    private static int applyMapping(int flags, FlagMapping[] flagToConfigMap) {
         int inputConfig = 0;
         for (final FlagMapping mapping : flagToConfigMap) {
             final boolean flagSet = (flags & mapping.mFlag) != 0;
@@ -131,7 +131,7 @@
     }
 
     @InputConfigFlags
-    private static int computeMask(List<FlagMapping> flagToConfigMap) {
+    private static int computeMask(FlagMapping[] flagToConfigMap) {
         int mask = 0;
         for (final FlagMapping mapping : flagToConfigMap) {
             mask |= mapping.mInputConfig;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index a84ebd9..22ca82a 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -290,22 +290,6 @@
         }
     }
 
-    @Override
-    public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
-        synchronized (mService.mGlobalLock) {
-            mService.setMousePointerDisplayId(displayId);
-            if (displayId == Display.INVALID_DISPLAY) return;
-
-            final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-            if (dc == null) {
-                Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
-                        + " that does not have a valid DisplayContent.");
-                return;
-            }
-            mService.restorePointerIconLocked(dc, x, y);
-        }
-    }
-
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6003c1b..be8c2ae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -911,7 +911,6 @@
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
             dc.updateKeepClearAreas();
-            dc.updateTouchExcludeRegion();
         });
 
         // Check to see if we are now in a state where the screen should
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index bb86460..3b3eeb4 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -740,16 +740,6 @@
     }
 
     @Override
-    public void updatePointerIcon(IWindow window) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mService.updatePointerIcon(window);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
     public void updateTapExcludeRegion(IWindow window, Region region) {
         final long identity = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
deleted file mode 100644
index ac244c7..0000000
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
-import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.input.InputManagerGlobal;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.WindowManagerPolicyConstants.PointerEventListener;
-
-import com.android.server.wm.WindowManagerService.H;
-
-/**
- * 1. Adjust the top most focus display if touch down on some display.
- * 2. Adjust the pointer icon when cursor moves to the task bounds.
- */
-public class TaskTapPointerEventListener implements PointerEventListener {
-
-    private final Region mTouchExcludeRegion = new Region();
-    private final WindowManagerService mService;
-    private final DisplayContent mDisplayContent;
-    private final Rect mTmpRect = new Rect();
-    private int mPointerIconType = TYPE_NOT_SPECIFIED;
-
-    public TaskTapPointerEventListener(WindowManagerService service,
-            DisplayContent displayContent) {
-        // TODO(b/315321016): Remove this class when the flag rollout is complete.
-        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
-            throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
-        }
-        mService = service;
-        mDisplayContent = displayContent;
-    }
-
-    private void restorePointerIcon(int x, int y) {
-        if (mPointerIconType != TYPE_NOT_SPECIFIED) {
-            mPointerIconType = TYPE_NOT_SPECIFIED;
-            // Find the underlying window and ask it to restore the pointer icon.
-            mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
-            mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
-                    x, y, mDisplayContent).sendToTarget();
-        }
-    }
-
-    @Override
-    public void onPointerEvent(MotionEvent motionEvent) {
-        switch (motionEvent.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                final int x;
-                final int y;
-                if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) {
-                    x = (int) motionEvent.getXCursorPosition();
-                    y = (int) motionEvent.getYCursorPosition();
-                } else {
-                    x = (int) motionEvent.getX();
-                    y = (int) motionEvent.getY();
-                }
-
-                synchronized (this) {
-                    if (!mTouchExcludeRegion.contains(x, y)) {
-                        mService.mTaskPositioningController.handleTapOutsideTask(
-                                mDisplayContent, x, y);
-                    }
-                }
-            }
-            break;
-            case MotionEvent.ACTION_HOVER_ENTER:
-            case MotionEvent.ACTION_HOVER_MOVE: {
-                final int x = (int) motionEvent.getX();
-                final int y = (int) motionEvent.getY();
-                if (mTouchExcludeRegion.contains(x, y)) {
-                    restorePointerIcon(x, y);
-                    break;
-                }
-                final Task task = mDisplayContent.findTaskForResizePoint(x, y);
-                int iconType = TYPE_NOT_SPECIFIED;
-                if (task != null) {
-                    task.getDimBounds(mTmpRect);
-                    if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
-                        if (x < mTmpRect.left) {
-                            iconType =
-                                (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
-                                (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
-                                TYPE_HORIZONTAL_DOUBLE_ARROW;
-                        } else if (x > mTmpRect.right) {
-                            iconType =
-                                (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
-                                (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
-                                TYPE_HORIZONTAL_DOUBLE_ARROW;
-                        } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
-                            iconType = TYPE_VERTICAL_DOUBLE_ARROW;
-                        }
-                    }
-                }
-                if (mPointerIconType != iconType) {
-                    mPointerIconType = iconType;
-                    if (mPointerIconType == TYPE_NOT_SPECIFIED) {
-                        // Find the underlying window and ask it restore the pointer icon.
-                        mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
-                        mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
-                                x, y, mDisplayContent).sendToTarget();
-                    } else {
-                        InputManagerGlobal.getInstance()
-                                .setPointerIconType(mPointerIconType);
-                    }
-                }
-            }
-            break;
-            case MotionEvent.ACTION_HOVER_EXIT: {
-                final int x = (int) motionEvent.getX();
-                final int y = (int) motionEvent.getY();
-                restorePointerIcon(x, y);
-            }
-            break;
-        }
-    }
-
-    void setTouchExcludeRegion(Region newRegion) {
-        synchronized (this) {
-           mTouchExcludeRegion.set(newRegion);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1543263..7ec31d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1648,14 +1648,6 @@
         }
 
         if (mController.useFullReadyTracking()) {
-            if (mReadyTracker.mMet.isEmpty()) {
-                Slog.e(TAG, "#" + mSyncId + ": No conditions provided");
-            } else {
-                for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) {
-                    Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: "
-                            + mReadyTracker.mConditions.get(i));
-                }
-            }
             for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
                         mSyncId, mReadyTracker.mMet.get(i));
@@ -3360,6 +3352,18 @@
         applyReady();
     }
 
+    @Override
+    public void onReadyTimeout() {
+        if (!mController.useFullReadyTracking()) {
+            Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed
+                    + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth
+                    + " group=" + mReadyTrackerOld.mReadyGroups);
+            return;
+        }
+        Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet);
+        Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions);
+    }
+
     /**
      * Represents a condition that must be met before an associated transition can be considered
      * ready.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1559be5..e02e5be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -81,15 +81,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -203,7 +199,6 @@
 import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManager;
 import android.hardware.input.InputSettings;
 import android.net.Uri;
 import android.os.Binder;
@@ -289,8 +284,6 @@
 import android.view.InsetsState;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
 import android.view.RemoteAnimationAdapter;
 import android.view.ScrollCaptureResponse;
 import android.view.Surface;
@@ -1527,18 +1520,6 @@
         }
     }
 
-    static boolean excludeWindowTypeFromTapOutTask(int windowType) {
-        switch (windowType) {
-            case TYPE_STATUS_BAR:
-            case TYPE_NOTIFICATION_SHADE:
-            case TYPE_NAVIGATION_BAR:
-            case TYPE_INPUT_METHOD_DIALOG:
-            case TYPE_VOLUME_OVERLAY:
-                return true;
-        }
-        return false;
-    }
-
     public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
             int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
             InputChannel outInputChannel, InsetsState outInsetsState,
@@ -1837,10 +1818,6 @@
                 displayContent.mWinAddedSinceNullFocus.add(win);
             }
 
-            if (excludeWindowTypeFromTapOutTask(type)) {
-                displayContent.mTapExcludedWindows.add(win);
-            }
-
             win.mSession.onWindowAdded(win);
             mWindowMap.put(client.asBinder(), win);
             win.initAppOpsState();
@@ -5720,7 +5697,6 @@
 
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
-        public static final int RESTORE_POINTER_ICON = 55;
         public static final int SET_HAS_OVERLAY_UI = 58;
         public static final int ANIMATION_FAILSAFE = 60;
         public static final int RECOMPUTE_FOCUS = 61;
@@ -5953,12 +5929,6 @@
                     }
                     break;
                 }
-                case RESTORE_POINTER_ICON: {
-                    synchronized (mGlobalLock) {
-                        restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
-                    }
-                    break;
-                }
                 case SET_HAS_OVERLAY_UI: {
                     mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
                     break;
@@ -7578,144 +7548,6 @@
         }
     }
 
-    // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
-    // TODO(b/293587049): Remove after the refactoring is fully rolled out.
-    @Nullable
-    final MousePositionTracker mMousePositionTracker =
-            com.android.input.flags.Flags.enablePointerChoreographer() ? null
-                    : new MousePositionTracker();
-
-    private static class MousePositionTracker implements PointerEventListener {
-        private boolean mLatestEventWasMouse;
-        private float mLatestMouseX;
-        private float mLatestMouseY;
-
-        /**
-         * The display that the pointer (mouse cursor) is currently shown on. This is updated
-         * directly by InputManagerService when the pointer display changes.
-         */
-        private int mPointerDisplayId = INVALID_DISPLAY;
-
-        /**
-         * Update the mouse cursor position as a result of a mouse movement.
-         * @return true if the position was successfully updated, false otherwise.
-         */
-        boolean updatePosition(int displayId, float x, float y) {
-            synchronized (this) {
-                mLatestEventWasMouse = true;
-
-                if (displayId != mPointerDisplayId) {
-                    // The display of the position update does not match the display on which the
-                    // mouse pointer is shown, so do not update the position.
-                    return false;
-                }
-                mLatestMouseX = x;
-                mLatestMouseY = y;
-                return true;
-            }
-        }
-
-        void setPointerDisplayId(int displayId) {
-            synchronized (this) {
-                mPointerDisplayId = displayId;
-            }
-        }
-
-        @Override
-        public void onPointerEvent(MotionEvent motionEvent) {
-            if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
-                updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
-                        motionEvent.getRawY());
-            } else {
-                synchronized (this) {
-                    mLatestEventWasMouse = false;
-                }
-            }
-        }
-    };
-
-    void updatePointerIcon(IWindow client) {
-        if (mMousePositionTracker == null) {
-            return;
-        }
-        int pointerDisplayId;
-        float mouseX, mouseY;
-
-        synchronized(mMousePositionTracker) {
-            if (!mMousePositionTracker.mLatestEventWasMouse) {
-                return;
-            }
-            mouseX = mMousePositionTracker.mLatestMouseX;
-            mouseY = mMousePositionTracker.mLatestMouseY;
-            pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
-        }
-
-        synchronized (mGlobalLock) {
-            if (mDragDropController.dragDropActiveLocked()) {
-                // Drag cursor overrides the app cursor.
-                return;
-            }
-            WindowState callingWin = windowForClientLocked(null, client, false);
-            if (callingWin == null) {
-                ProtoLog.w(WM_ERROR, "Bad requesting window %s", client);
-                return;
-            }
-            final DisplayContent displayContent = callingWin.getDisplayContent();
-            if (displayContent == null) {
-                return;
-            }
-            if (pointerDisplayId != displayContent.getDisplayId()) {
-                // Do not let the pointer icon be updated by a window on a different display.
-                return;
-            }
-            WindowState windowUnderPointer =
-                    displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
-            if (windowUnderPointer != callingWin) {
-                return;
-            }
-            try {
-                windowUnderPointer.mClient.updatePointerIcon(
-                        windowUnderPointer.translateToWindowX(mouseX),
-                        windowUnderPointer.translateToWindowY(mouseY));
-            } catch (RemoteException e) {
-                ProtoLog.w(WM_ERROR, "unable to update pointer icon");
-            }
-        }
-    }
-
-    void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
-        if (mMousePositionTracker == null) {
-            return;
-        }
-        // Mouse position tracker has not been getting updates while dragging, update it now.
-        if (!mMousePositionTracker.updatePosition(
-                displayContent.getDisplayId(), latestX, latestY)) {
-            // The mouse position could not be updated, so ignore this request.
-            return;
-        }
-
-        WindowState windowUnderPointer =
-                displayContent.getTouchableWinAtPointLocked(latestX, latestY);
-        if (windowUnderPointer != null) {
-            try {
-                windowUnderPointer.mClient.updatePointerIcon(
-                        windowUnderPointer.translateToWindowX(latestX),
-                        windowUnderPointer.translateToWindowY(latestY));
-            } catch (RemoteException e) {
-                ProtoLog.w(WM_ERROR, "unable to restore pointer icon");
-            }
-        } else {
-            mContext.getSystemService(InputManager.class)
-                    .setPointerIconType(PointerIcon.TYPE_DEFAULT);
-        }
-    }
-    void setMousePointerDisplayId(int displayId) {
-        if (mMousePositionTracker == null) {
-            return;
-        }
-        mMousePositionTracker.setPointerDisplayId(displayId);
-    }
-
     /**
      * Update a tap exclude region in the window identified by the provided id. Touches down on this
      * region will not:
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 510f69a..dddc7b1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2359,18 +2359,12 @@
         }
 
         final int type = mAttrs.type;
-        if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
-            dc.mTapExcludedWindows.remove(this);
-        }
 
         if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
             mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
                     /*isShown=*/ false);
         }
 
-        // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will
-        // not do anything.
-        dc.mTapExcludeProvidingWindows.remove(this);
         dc.getDisplayPolicy().removeWindowLw(this);
 
         disposeInputChannel();
@@ -5526,18 +5520,10 @@
         // Clear the tap excluded region if the region passed in is null or empty.
         if (region == null || region.isEmpty()) {
             mTapExcludeRegion.setEmpty();
-            // Remove this window from mTapExcludeProvidingWindows since it won't be providing
-            // tap exclude regions.
-            currentDisplay.mTapExcludeProvidingWindows.remove(this);
         } else {
             mTapExcludeRegion.set(region);
-            // Make sure that this window is registered as one that provides a tap exclude region
-            // for its containing display.
-            currentDisplay.mTapExcludeProvidingWindows.add(this);
         }
 
-        // Trigger touch exclude region update on current display.
-        currentDisplay.updateTouchExcludeRegion();
         // Trigger touchable region update for this window.
         currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */);
     }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 62f5b89..ba89fda 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -272,22 +272,23 @@
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
-    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
                                                                    const std::string& name,
                                                                    gui::Pid pid);
     status_t removeInputChannel(const sp<IBinder>& connectionToken);
     status_t pilferPointers(const sp<IBinder>& token);
 
-    void displayRemoved(JNIEnv* env, int32_t displayId);
-    void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
-    void setFocusedDisplay(int32_t displayId);
+    void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId);
+    void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+                               jobject applicationHandleObj);
+    void setFocusedDisplay(ui::LogicalDisplayId displayId);
     void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
-    void setPointerDisplayId(int32_t displayId);
+    void setPointerDisplayId(ui::LogicalDisplayId displayId);
     int32_t getMousePointerSpeed();
     void setPointerSpeed(int32_t speed);
-    void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
+    void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
     void setTouchpadPointerSpeed(int32_t speed);
     void setTouchpadNaturalScrollingEnabled(bool enabled);
     void setTouchpadTapToClickEnabled(bool enabled);
@@ -300,13 +301,13 @@
     void reloadPointerIcons();
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
-                        int32_t displayId, DeviceId deviceId, int32_t pointerId,
+                        ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
                         const sp<IBinder>& inputToken);
-    void setPointerIconVisibility(int32_t displayId, bool visible);
+    void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible);
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
-    FloatPoint getMouseCursorPosition(int32_t displayId);
+    FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
     void setStylusPointerIconEnabled(bool enabled);
     void setInputMethodConnectionIsActive(bool isActive);
 
@@ -325,7 +326,7 @@
     void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
     bool isInputMethodConnectionActive() override;
     std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId) override;
+            ui::LogicalDisplayId associatedDisplayId) override;
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -348,13 +349,15 @@
     void notifyVibratorState(int32_t deviceId, bool isOn) override;
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
     void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
-    void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
-                                       nsecs_t when, uint32_t& policyFlags) override;
+    void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source,
+                                       int32_t action, nsecs_t when,
+                                       uint32_t& policyFlags) override;
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                           uint32_t policyFlags) override;
     std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                                  uint32_t policyFlags) override;
-    void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
+    void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                          ui::LogicalDisplayId displayId) override;
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -363,11 +366,13 @@
 
     /* --- PointerControllerPolicyInterface implementation --- */
 
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId);
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId);
+    virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId);
+    virtual void loadPointerResources(PointerResources* outResources,
+                                      ui::LogicalDisplayId displayId);
     virtual void loadAdditionalMouseResources(
             std::map<PointerIconStyle, SpriteIcon>* outResources,
-            std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId);
+            std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+            ui::LogicalDisplayId displayId);
     virtual PointerIconStyle getDefaultPointerIconId();
     virtual PointerIconStyle getDefaultStylusIconId();
     virtual PointerIconStyle getCustomPointerIconId();
@@ -375,7 +380,8 @@
     /* --- PointerChoreographerPolicyInterface implementation --- */
     std::shared_ptr<PointerControllerInterface> createPointerController(
             PointerControllerInterface::ControllerType type) override;
-    void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
+    void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+                                       const FloatPoint& position) override;
 
     /* --- InputFilterPolicyInterface implementation --- */
     void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -399,7 +405,7 @@
         int32_t pointerSpeed{0};
 
         // Displays on which its associated mice will have pointer acceleration disabled.
-        std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
+        std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{};
 
         // True if pointer gestures are enabled.
         bool pointerGesturesEnabled{true};
@@ -417,7 +423,7 @@
         std::set<int32_t> disabledInputDevices{};
 
         // Associated Pointer controller display.
-        int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
+        ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT};
 
         // True if stylus button reporting through motion events is enabled.
         bool stylusButtonMotionEventsEnabled{true};
@@ -450,7 +456,7 @@
     void updateInactivityTimeoutLocked();
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
-    sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
+    sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId);
     static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
     template <typename T>
     std::unordered_map<std::string, T> readMapFromInterleavedJavaArray(
@@ -459,7 +465,7 @@
 
     void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
             REQUIRES(mLock);
-    PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type);
+    PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type);
 
     static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
 };
@@ -490,7 +496,9 @@
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
         dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
-                             dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
+                             dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled,
+                                     streamableToString)
+                                     .c_str());
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                              toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
@@ -552,7 +560,7 @@
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
-        int32_t displayId, const std::string& name, gui::Pid pid) {
+        ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
 }
@@ -735,7 +743,7 @@
     }
 }
 
-PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId,
+PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId,
                                                 PointerIconStyle type) {
     if (type == PointerIconStyle::TYPE_CUSTOM) {
         LOG(FATAL) << __func__ << ": Cannot load non-system icon type";
@@ -766,7 +774,7 @@
     return pc;
 }
 
-void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId,
+void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId,
                                                        const FloatPoint& position) {
     // Notify the Reader so that devices can be reconfigured.
     { // acquire lock
@@ -775,7 +783,7 @@
             return;
         }
         mLocked.pointerDisplayId = pointerDisplayId;
-        ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId);
+        ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str());
     } // release lock
     mInputManager->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::Change::DISPLAY_INFO);
@@ -795,7 +803,7 @@
     checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
 }
 
-sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
+sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
     JNIEnv* env = jniEnv();
     jlong nativeSurfaceControlPtr =
             env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers,
@@ -817,9 +825,10 @@
         layer = -1;
     }
     mLocked.spriteController =
-            std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) {
-                return getParentSurfaceForPointers(displayId);
-            });
+            std::make_shared<SpriteController>(mLooper, layer,
+                                               [this](ui::LogicalDisplayId displayId) {
+                                                   return getParentSurfaceForPointers(displayId);
+                                               });
     // The SpriteController needs to be shared pointer because the handler callback needs to hold
     // a weak reference so that we can avoid racy conditions when the controller is being destroyed.
     mLocked.spriteController->setHandlerController(mLocked.spriteController);
@@ -1021,8 +1030,7 @@
 
     jobject tokenObj = javaObjectForIBinder(env, token);
     if (tokenObj) {
-        env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
-                tokenObj);
+        env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj);
         checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
     }
 }
@@ -1108,12 +1116,12 @@
     checkAndClearExceptionFromCallback(env, "notifyVibratorState");
 }
 
-void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) {
     mInputManager->getDispatcher().displayRemoved(displayId);
 }
 
-void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
-        jobject applicationHandleObj) {
+void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+                                               jobject applicationHandleObj) {
     if (!applicationHandleObj) {
         return;
     }
@@ -1123,7 +1131,7 @@
     mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
 }
 
-void NativeInputManager::setFocusedDisplay(int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) {
     mInputManager->getDispatcher().setFocusedDisplay(displayId);
 }
 
@@ -1151,7 +1159,7 @@
     });
 }
 
-void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) {
     mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId);
 }
 
@@ -1176,7 +1184,8 @@
             InputReaderConfiguration::Change::POINTER_SPEED);
 }
 
-void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
+void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId,
+                                                            bool enabled) {
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -1186,8 +1195,8 @@
             return;
         }
 
-        ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
-              displayId);
+        ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled),
+              displayId.toString().c_str());
         if (enabled) {
             mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
         } else {
@@ -1326,8 +1335,9 @@
 }
 
 bool NativeInputManager::setPointerIcon(
-        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
-        DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) {
+        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+        ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
+        const sp<IBinder>& inputToken) {
     if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId,
                                                           pointerId)) {
         LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId
@@ -1339,7 +1349,7 @@
     return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
 }
 
-void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
     mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
 }
 
@@ -1394,7 +1404,7 @@
 }
 
 std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay(
-        int32_t associatedDisplayId) {
+        ui::LogicalDisplayId associatedDisplayId) {
     return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId);
 }
 
@@ -1469,9 +1479,9 @@
     handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
 }
 
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
-                                                       int32_t action, nsecs_t when,
-                                                       uint32_t& policyFlags) {
+void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId,
+                                                       uint32_t source, int32_t action,
+                                                       nsecs_t when, uint32_t& policyFlags) {
     ATRACE_CALL();
     // Policy:
     // - Ignore untrusted events and pass them along.
@@ -1590,7 +1600,8 @@
     return fallbackEvent;
 }
 
-void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) {
+void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                                          ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
 }
@@ -1621,13 +1632,14 @@
             InputReaderConfiguration::Change::POINTER_CAPTURE);
 }
 
-void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
+void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
     *icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW));
 }
 
-void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) {
+void NativeInputManager::loadPointerResources(PointerResources* outResources,
+                                              ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
@@ -1641,7 +1653,8 @@
 
 void NativeInputManager::loadAdditionalMouseResources(
         std::map<PointerIconStyle, SpriteIcon>* outResources,
-        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) {
+        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+        ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
@@ -1708,7 +1721,7 @@
             InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
 }
 
-FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
+FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
     return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
 }
 
@@ -1874,7 +1887,7 @@
                                         jstring nameObj, jint pid) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    if (displayId == ADISPLAY_ID_NONE) {
+    if (displayId == ui::ADISPLAY_ID_NONE.val()) {
         std::string message = "InputChannel used as a monitor must be associated with a display";
         jniThrowRuntimeException(env, message.c_str());
         return nullptr;
@@ -1884,7 +1897,7 @@
     std::string name = nameChars.c_str();
 
     base::Result<std::unique_ptr<InputChannel>> inputChannel =
-            im->createInputMonitor(displayId, name, gui::Pid{pid});
+            im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid});
 
     if (!inputChannel.ok()) {
         std::string message = inputChannel.error().message();
@@ -1931,7 +1944,8 @@
 
     return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid},
                                                                  gui::Uid{static_cast<uid_t>(uid)},
-                                                                 hasPermission, displayId);
+                                                                 hasPermission,
+                                                                 ui::LogicalDisplayId{displayId});
 }
 
 static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
@@ -2023,20 +2037,20 @@
 static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->displayRemoved(env, displayId);
+    im->displayRemoved(env, ui::LogicalDisplayId{displayId});
 }
 
 static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
                                         jobject applicationHandleObj) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setFocusedApplication(env, displayId, applicationHandleObj);
+    im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj);
 }
 
 static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setFocusedDisplay(displayId);
+    im->setFocusedDisplay(ui::LogicalDisplayId{displayId});
 }
 
 static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
@@ -2092,8 +2106,8 @@
 
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken,
-                                                                      static_cast<int32_t>(
-                                                                              displayId))) {
+                                                                      ui::LogicalDisplayId{
+                                                                              displayId})) {
         return JNI_TRUE;
     } else {
         return JNI_FALSE;
@@ -2116,7 +2130,7 @@
                                                      jint displayId, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setMousePointerAccelerationEnabled(displayId, enabled);
+    im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled);
 }
 
 static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2486,7 +2500,7 @@
         icon = pointerIcon.style;
     }
 
-    return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId,
+    return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId,
                               ibinderForJavaObject(env, inputTokenObj));
 }
 
@@ -2494,13 +2508,14 @@
                                            jboolean visible) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setPointerIconVisibility(displayId, visible);
+    im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible);
 }
 
 static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                            jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
+    return im->getInputManager()->getReader().canDispatchToDisplay(deviceId,
+                                                                   ui::LogicalDisplayId{displayId});
 }
 
 static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
@@ -2512,8 +2527,9 @@
 static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
                                                          jint displayId, jboolean isEligible) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
-                                                                                  isEligible);
+    im->getInputManager()
+            ->getDispatcher()
+            .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible);
 }
 
 static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
@@ -2649,7 +2665,7 @@
 
 static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->setPointerDisplayId(displayId);
+    im->setPointerDisplayId(ui::LogicalDisplayId{displayId});
 }
 
 static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
@@ -2667,7 +2683,7 @@
 static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
                                                 jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    const auto p = im->getMouseCursorPosition(displayId);
+    const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId});
     const std::array<float, 2> arr = {{p.x, p.y}};
     jfloatArray outArr = env->NewFloatArray(2);
     env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index d0b290c..0733968 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -99,7 +99,7 @@
 }
 
 void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
-                                                     int32_t displayId) {
+                                                     ui::LogicalDisplayId displayId) {
     if (gPowerManagerServiceObj) {
         // Throttle calls into user activity by event type.
         // We're a little conservative about argument checking here in case the caller
@@ -124,8 +124,8 @@
         JNIEnv* env = AndroidRuntime::getJNIEnv();
 
         env->CallVoidMethod(gPowerManagerServiceObj,
-                gPowerManagerServiceClassInfo.userActivityFromNative,
-                nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0);
+                            gPowerManagerServiceClassInfo.userActivityFromNative,
+                            nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0);
         checkAndClearExceptionFromCallback(env, "userActivityFromNative");
     }
 }
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index 36aaceb..ed7fa7c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -19,6 +19,7 @@
 
 #include <nativehelper/JNIHelp.h>
 #include <powermanager/PowerManager.h>
+#include <ui/LogicalDisplayId.h>
 #include <utils/Timers.h>
 
 #include "jni.h"
@@ -26,7 +27,7 @@
 namespace android {
 
 extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
-                                                            int32_t displayId);
+                                                            ui::LogicalDisplayId displayId);
 
 } // namespace android
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 375fc5a..9eb7b22 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21605,12 +21605,9 @@
                                 == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
             }
 
-            if (Flags.headlessSingleMinTargetSdk()
-                    && mInjector.userManagerIsHeadlessSystemUserMode()
-                    && isSingleUserMode
-                    && !mInjector.isChangeEnabled(
-                            PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(),
-                    caller.getUserId())) {
+            if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
+                    && isSingleUserMode && !mInjector.isChangeEnabled(
+                    PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
                 throw new IllegalStateException("Device admin is not targeting Android V.");
             }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4bf3ff4..09eef45 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -196,19 +196,27 @@
         Binder.withCleanCallingIdentity(() -> {
             PackageManagerInternal pmi =
                     LocalServices.getService(PackageManagerInternal.class);
+            AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
             pmi.setOwnerProtectedPackages(userId,
                     packages == null ? null : packages.stream().toList());
             LocalServices.getService(UsageStatsManagerInternal.class)
                     .setAdminProtectedPackages(
                             packages == null ? null : new ArraySet<>(packages), userId);
 
-            if (Flags.disallowUserControlBgUsageFix()) {
-                if (packages == null) {
-                    return;
+            if (packages == null || packages.isEmpty()) {
+                return;
+            }
+
+            for (int user : resolveUsers(userId)) {
+                if (Flags.disallowUserControlBgUsageFix()) {
+                    setBgUsageAppOp(packages, pmi, user, appOpsManager);
                 }
-                final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-                resolveUsers(userId).forEach(
-                        user -> setBgUsageAppOp(packages, pmi, user, appOpsManager));
+                if (Flags.disallowUserControlStoppedStateFix()) {
+                    for (String packageName : packages) {
+                        pmi.setPackageStoppedState(packageName, false, user);
+                    }
+                }
             }
         });
         return true;
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index b155829..6499556 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2046,8 +2046,20 @@
                     writer.println("Unknown app ID $appId.")
                 }
             }
+        } else if (args[0] == "--package" && args.size == 2) {
+            val packageName = args[1]
+            service.getState {
+                val packageState = state.externalState.packageStates[packageName]
+                if (packageState != null) {
+                    writer.dumpAppIdState(packageState.appId, state, indexedSetOf(packageName))
+                } else {
+                    writer.println("Unknown package $packageName.")
+                }
+            }
         } else {
-            writer.println("Usage: dumpsys permission [--app-id APP_ID]")
+            writer.println(
+                "Usage: dumpsys permissionmgr [--app-id <APP_ID>] [--package <PACKAGE_NAME>]"
+            )
         }
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 80f38eb..e5685c7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1790,6 +1790,7 @@
         verify(mHolder.animator).animateTo(eq(brightness),
                 /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
                 /* ignoreAnimationLimits= */ anyBoolean());
+        verify(mHolder.brightnessSetting).setBrightness(brightness);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
new file mode 100644
index 0000000..7f2327aa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.testing.TestableContext;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class AppOpsDeviceAwareServiceTest {
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+    @Rule
+    public VirtualDeviceRule virtualDeviceRule =
+            VirtualDeviceRule.withAdditionalPermissions(
+                    Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                    Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                    Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                    Manifest.permission.GET_APP_OPS_STATS);
+
+    private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
+    private static final String ATTRIBUTION_TAG_2 = "attributionTag2";
+    private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+    private final PermissionManager mPermissionManager =
+            mContext.getSystemService(PermissionManager.class);
+
+    private VirtualDeviceManager.VirtualDevice mVirtualDevice;
+
+    @Before
+    public void setUp() {
+        mVirtualDevice =
+                virtualDeviceRule.createManagedVirtualDevice(
+                        new VirtualDeviceParams.Builder()
+                                .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+                                .build());
+
+        mPermissionManager.grantRuntimePermission(
+                mContext.getOpPackageName(),
+                Manifest.permission.CAMERA,
+                mVirtualDevice.getPersistentDeviceId());
+
+        mPermissionManager.grantRuntimePermission(
+                mContext.getOpPackageName(),
+                Manifest.permission.CAMERA,
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+    @Test
+    public void noteProxyOp_proxyAppOnDefaultDevice() {
+        AppOpsManager.OpEventProxyInfo proxyInfo =
+                noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT);
+        assertThat(proxyInfo.getDeviceId())
+                .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+    @Test
+    public void noteProxyOp_proxyAppOnRemoteDevice() {
+        AppOpsManager.OpEventProxyInfo proxyInfo =
+                noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId());
+        assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId());
+    }
+
+    private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) {
+        AttributionSource proxiedAttributionSource =
+                new AttributionSource.Builder(Process.myUid())
+                        .setPackageName(mContext.getOpPackageName())
+                        .setAttributionTag(ATTRIBUTION_TAG_2)
+                        .setDeviceId(mVirtualDevice.getDeviceId())
+                        .build();
+
+        AttributionSource proxyAttributionSource =
+                new AttributionSource.Builder(Process.myUid())
+                        .setPackageName(mContext.getOpPackageName())
+                        .setAttributionTag(ATTRIBUTION_TAG_1)
+                        .setDeviceId(proxyAppDeviceId)
+                        .setNextAttributionSource(proxiedAttributionSource)
+                        .build();
+
+        int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false);
+        assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED);
+
+        List<AppOpsManager.PackageOps> packagesOps =
+                mAppOpsManager.getPackagesForOps(
+                        new String[] {AppOpsManager.OPSTR_CAMERA},
+                        mVirtualDevice.getPersistentDeviceId());
+
+        AppOpsManager.PackageOps packageOps =
+                packagesOps.stream()
+                        .filter(
+                                pkg ->
+                                        Objects.equals(
+                                                pkg.getPackageName(), mContext.getOpPackageName()))
+                        .findFirst()
+                        .orElseThrow();
+
+        AppOpsManager.OpEntry opEntry =
+                packageOps.getOps().stream()
+                        .filter(op -> op.getOp() == OP_CAMERA)
+                        .findFirst()
+                        .orElseThrow();
+
+        return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
index 0716a5c..3698d6f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
+import android.content.Context;
 import android.content.Intent;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.UserHandle;
@@ -75,13 +76,15 @@
     @Test
     public void testFingerprintRegisterReceiver() {
         initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
-        verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
+        verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
     }
 
     @Test
     public void testFaceRegisterReceiver() {
         initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
-        verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
+        verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 40ecaf1..7dd1847 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -42,6 +42,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.Process;
@@ -488,7 +489,7 @@
 
         setUpPackageInstalled(pkgNameA);
 
-        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
@@ -504,7 +505,7 @@
 
         setUpPackageInstalled(pkgNameB);
 
-        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
@@ -518,6 +519,66 @@
     }
 
     @Test
+    public void testRestore_appInstalledAfterSUW_restoresFromStage_ArchiveEnabled()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru";
+        String langTagsB = "hi,fr";
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
+        writeTestPayload(out, pkgLocalesMap);
+        setUpPackageNotInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+
+        setUpPackageInstalled(pkgNameA);
+
+        mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
+                .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+
+        pkgLocalesMap.remove(pkgNameA);
+
+        verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+
+        setUpPackageInstalled(pkgNameB);
+
+        mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+                .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+            new ArraySet<>(Arrays.asList(pkgNameB)));
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
     public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -535,7 +596,7 @@
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
 
-        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, new Bundle());
 
         // Since locales are already set, we should not restore anything for it.
         verifyNothingRestored();
@@ -612,7 +673,7 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
         setUpPackageInstalled(pkgNameA);
 
-        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
                 pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false,
@@ -627,7 +688,7 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         setUpPackageInstalled(pkgNameB);
 
-        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
                 any(), anyBoolean(), anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5e2fe6a..2d672b8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5969,6 +5969,8 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
         assertThat(captor.getValue().shouldPostSilently()).isTrue();
     }
 
@@ -8798,6 +8800,8 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
         assertThat(captor.getValue().shouldPostSilently()).isTrue();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 44d1b54..87395a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -113,15 +113,10 @@
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -131,7 +126,6 @@
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindowManager;
 import android.view.InsetsState;
-import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -151,7 +145,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.WmDisplayCutout;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -178,10 +171,6 @@
 @RunWith(WindowTestRunner.class)
 public class DisplayContentTests extends WindowTestsBase {
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows() {
@@ -514,44 +503,6 @@
         assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
     }
 
-    /**
-     * Tests tapping on a root task in different display results in window gaining focus.
-     */
-    @Test
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM)
-    public void testInputEventBringsCorrectDisplayInFocus() {
-        DisplayContent dc0 = mWm.getDefaultDisplayContentLocked();
-        // Create a second display
-        final DisplayContent dc1 = createNewDisplay();
-
-        // Add root task with activity.
-        final Task rootTask0 = createTask(dc0);
-        final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */);
-        final ActivityRecord activity = createNonAttachedActivityRecord(dc0);
-        task0.addChild(activity, 0);
-        dc0.configureDisplayPolicy();
-        assertNotNull(dc0.mTapDetector);
-
-        final Task rootTask1 = createTask(dc1);
-        final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
-        final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0);
-        task1.addChild(activity1, 0);
-        dc1.configureDisplayPolicy();
-        assertNotNull(dc1.mTapDetector);
-
-        // tap on primary display.
-        tapOnDisplay(dc0);
-        // Check focus is on primary display.
-        assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
-                dc0.findFocusedWindow());
-
-        // Tap on secondary display.
-        tapOnDisplay(dc1);
-        // Check focus is on secondary.
-        assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
-                dc1.findFocusedWindow());
-    }
-
     @Test
     public void testFocusedWindowMultipleDisplays() {
         doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
@@ -2959,33 +2910,4 @@
             throw new RuntimeException(e);
         }
     }
-
-    private void tapOnDisplay(final DisplayContent dc) {
-        final DisplayMetrics dm = dc.getDisplayMetrics();
-        final float x = dm.widthPixels / 2;
-        final float y = dm.heightPixels / 2;
-        final long downTime = SystemClock.uptimeMillis();
-        final long eventTime = SystemClock.uptimeMillis() + 100;
-        // sending ACTION_DOWN
-        final MotionEvent downEvent = MotionEvent.obtain(
-                downTime,
-                downTime,
-                MotionEvent.ACTION_DOWN,
-                x,
-                y,
-                0 /*metaState*/);
-        downEvent.setDisplayId(dc.getDisplayId());
-        dc.mTapDetector.onPointerEvent(downEvent);
-
-        // sending ACTION_UP
-        final MotionEvent upEvent = MotionEvent.obtain(
-                downTime,
-                eventTime,
-                MotionEvent.ACTION_UP,
-                x,
-                y,
-                0 /*metaState*/);
-        upEvent.setDisplayId(dc.getDisplayId());
-        dc.mTapDetector.onPointerEvent(upEvent);
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index cda26f1..4fc222b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -95,10 +95,6 @@
     }
 
     @Override
-    public void updatePointerIcon(float x, float y) throws RemoteException {
-    }
-
-    @Override
     public void dispatchWindowShown() throws RemoteException {
     }
 
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 3b9ee80..709f58d 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -169,12 +169,6 @@
         localService.setDisplayViewports(viewports)
         verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
         verify(native).setPointerDisplayId(displayId)
-
-        val x = 42f
-        val y = 314f
-        service.onPointerDisplayIdChanged(displayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
     }
 
     @Test
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index e3f84c1..f126000 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -24,6 +24,7 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.os.Process;
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
@@ -47,8 +48,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
-                + getDisplayId());
+        Log.v(TAG, "Create MainActivity as user "
+                + Process.myUserHandle().getIdentifier() + " on display "
+                + getDisplay().getDisplayId());
         setContentView(R.layout.main_activity);
         mImm = getSystemService(InputMethodManager.class);
         mEditor = requireViewById(R.id.edit_text);