Merge "Fix a race condition in BaseBundle." into main
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/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
index eea3b84..373355a 100755
--- a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
+++ b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
@@ -61,8 +61,8 @@
 imports = """
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -118,4 +118,4 @@
         print("        default void f{}() {{}}".format(i*imt_size + j))
     print("    }")
 
-print("}")
\ No newline at end of file
+print("}")
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
index f3a1fff..01abdb6 100755
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
@@ -160,8 +160,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 852b00b..d5a58d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -1771,7 +1771,13 @@
                     final int logicalIndex = mapping.getLogicalSlotIndex();
                     if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
                         // Callback already exists. No need to create a new one or remove it.
-                        callbacksToRemove.remove(logicalIndex);
+                        for (int i = callbacksToRemove.size() - 1; i >= 0; i--) {
+                            if (callbacksToRemove.get(i) == logicalIndex) {
+                                callbacksToRemove.remove(i);
+                                break;
+                            }
+                        }
+
                         continue;
                     }
                     final LogicalIndexCarrierPrivilegesCallback callback =
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eabe1f1..ba1bcc8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10342,7 +10342,7 @@
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
-    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
+    method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
     field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -10356,7 +10356,7 @@
   public static final class NetworkProviderInfo.Builder {
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
-    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+    method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e225c5b0..211fc96 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1774,16 +1774,16 @@
   }
 
   public final class InputManager {
-    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociation(@NonNull String, @NonNull String);
     method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String);
+    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
     method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
     method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
     method public int getMousePointerSpeed();
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
-    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociation(@NonNull String);
     method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String);
+    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String);
     field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
   }
 
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index c4fe061..6cc71e5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -975,14 +975,14 @@
     Method 'getHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.hardware.hdmi.HdmiControlManager#setHdmiCecVersion(int):
     Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String):
-    Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String):
     Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String):
-    Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+    Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String):
     Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+    Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback):
     Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes():
@@ -2035,6 +2035,10 @@
     New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isPrivateProfile()
 UnflaggedApi: android.credentials.CredentialProviderInfo#isPrimary():
     New API must be flagged with @FlaggedApi: method android.credentials.CredentialProviderInfo.isPrimary()
+UnflaggedApi: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+    New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.addUniqueIdAssociationByPort(String,String)
+UnflaggedApi: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+    New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.removeUniqueIdAssociationByPort(String)
 UnflaggedApi: android.media.AudioManager#enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>):
     New API must be flagged with @FlaggedApi: method android.media.AudioManager.enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>)
 UnflaggedApi: android.media.AudioManager#exitAudioFocusFreezeForTest():
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index caaaf51..d4812dd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -50,6 +50,7 @@
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
 import android.app.RemoteServiceException.CrashedByAdbException;
 import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
 import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -2236,6 +2237,9 @@
             case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
                 throw generateForegroundServiceDidNotStartInTimeException(message, extras);
 
+            case ForegroundServiceDidNotStopInTimeException.TYPE_ID:
+                throw generateForegroundServiceDidNotStopInTimeException(message, extras);
+
             case CannotPostForegroundServiceNotificationException.TYPE_ID:
                 throw new CannotPostForegroundServiceNotificationException(message);
 
@@ -2266,6 +2270,15 @@
         throw new ForegroundServiceDidNotStartInTimeException(message, inner);
     }
 
+    private ForegroundServiceDidNotStopInTimeException
+            generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) {
+        final String serviceClassName =
+                ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras);
+        final Exception inner = (serviceClassName == null) ? null
+                : Service.getStartForegroundServiceStackTrace(serviceClassName);
+        throw new ForegroundServiceDidNotStopInTimeException(message, inner);
+    }
+
     class H extends Handler {
         public static final int BIND_APPLICATION        = 110;
         @UnsupportedAppUsage
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 604b37d..0caea7f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6595,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) {
@@ -10352,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/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index c5ad110..c624c43 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -71,6 +71,33 @@
     }
 
     /**
+     * Exception used to crash an app process when it didn't stop after hitting its time limit.
+     *
+     * @hide
+     */
+    public static class ForegroundServiceDidNotStopInTimeException extends RemoteServiceException {
+        /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+        public static final int TYPE_ID = 7;
+
+        private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname";
+
+        public ForegroundServiceDidNotStopInTimeException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+
+        public static Bundle createExtrasForService(@NonNull ComponentName service) {
+            Bundle b = new Bundle();
+            b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName());
+            return b;
+        }
+
+        @Nullable
+        public static String getServiceClassNameFromExtras(@Nullable Bundle extras) {
+            return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME);
+        }
+    }
+
+    /**
      * Exception used to crash an app process when the system received a RemoteException
      * while posting a notification of a foreground service.
      *
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 726064e..aaddaa6 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1198,8 +1198,7 @@
      * Callback called when a particular foreground service type has timed out.
      *
      * <p>This callback is meant to give the app a small grace period of a few seconds to finish
-     * the foreground service of the associated type - if it fails to do so, the app will be
-     * declared an ANR.
+     * the foreground service of the associated type - if it fails to do so, the app will crash.
      *
      * <p>The foreground service of the associated type can be stopped within the time limit by
      * {@link android.app.Service#stopSelf()},
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 9cf83b9..bb24fd1 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -60,3 +60,13 @@
          purpose: PURPOSE_BUGFIX
      }
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "enable_fgs_timeout_crash_behavior"
+     description: "Enable the new behavior where the app is crashed once an FGS times out."
+     bug: "339526947"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 9ef8b38..46c9e78 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -21,6 +21,7 @@
 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;
@@ -176,6 +177,10 @@
      * 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;
 
@@ -185,6 +190,10 @@
      *
      * <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;
@@ -383,17 +392,30 @@
                     }
                     mSupportsTransferOwnership = true;
                 } else if (tagName.equals("headless-system-user")) {
-                    String deviceOwnerModeStringValue =
-                            parser.getAttributeValue(null, "device-owner-mode");
+                    String deviceOwnerModeStringValue = null;
+                    if (Flags.headlessSingleUserCompatibilityFix()) {
+                        deviceOwnerModeStringValue = parser.getAttributeValue(
+                                 null, "headless-device-owner-mode");
+                    }
+                    if (deviceOwnerModeStringValue == null) {
+                        deviceOwnerModeStringValue =
+                                parser.getAttributeValue(null, "device-owner-mode");
+                    }
 
-                    if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) {
+                    if ("unsupported".equalsIgnoreCase(deviceOwnerModeStringValue)) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
-                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
+                    } else if ("affiliated".equalsIgnoreCase(deviceOwnerModeStringValue)) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
-                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
+                    } else if ("single_user".equalsIgnoreCase(deviceOwnerModeStringValue)) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
                     } else {
-                        throw new XmlPullParserException("headless-system-user mode must be valid");
+                        if (Flags.headlessSingleUserCompatibilityFix()) {
+                            Log.e(TAG, "Unknown headless-system-user mode: "
+                                    + deviceOwnerModeStringValue);
+                        } else {
+                            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 18914e1..3d6ec19 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,3 +313,23 @@
       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"
+    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/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
index 9f09d04..5a13255 100644
--- a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
+++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
@@ -48,6 +48,8 @@
     const int POLICY_TYPE_AUDIO = 1;
     const int POLICY_TYPE_RECENTS = 2;
     const int POLICY_TYPE_ACTIVITY = 3;
+    const int POLICY_TYPE_CLIPBOARD = 4;
+    const int POLICY_TYPE_CAMERA = 5;
 
     /**
      * Returns the IDs for all VirtualDevices where an app with the given is running.
@@ -62,4 +64,4 @@
      * Returns the device policy for the given virtual device and policy type.
      */
     int getDevicePolicy(int deviceId, int policyType);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 7f01a82..37f419d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -462,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/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 3d8ccaa..c70eff4 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Hide;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.credentials.selection.GetCredentialProviderData;
 import android.os.Parcel;
@@ -39,6 +41,9 @@
     @NonNull
     private final List<GetCredentialProviderData> mCandidateProviderDataList;
 
+    @Nullable
+    private final ComponentName mPrimaryProviderComponentName;
+
     @NonNull
     private final Intent mIntent;
 
@@ -48,13 +53,15 @@
     @Hide
     public GetCandidateCredentialsResponse(
             @NonNull List<GetCredentialProviderData> candidateProviderDataList,
-            @NonNull Intent intent
+            @NonNull Intent intent,
+            @Nullable ComponentName primaryProviderComponentName
     ) {
         Preconditions.checkCollectionNotEmpty(
                 candidateProviderDataList,
                 /*valueName=*/ "candidateProviderDataList");
         mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
         mIntent = intent;
+        mPrimaryProviderComponentName = primaryProviderComponentName;
     }
 
     /**
@@ -67,6 +74,16 @@
     }
 
     /**
+     * Returns the primary provider component name.
+     *
+     * @hide
+     */
+    @Nullable
+    public ComponentName getPrimaryProviderComponentName() {
+        return mPrimaryProviderComponentName;
+    }
+
+    /**
      * Returns candidate provider data list.
      *
      * @hide
@@ -83,12 +100,15 @@
 
         AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
         mIntent = in.readTypedObject(Intent.CREATOR);
+
+        mPrimaryProviderComponentName = in.readTypedObject(ComponentName.CREATOR);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedList(mCandidateProviderDataList);
         dest.writeTypedObject(mIntent, flags);
+        dest.writeTypedObject(mPrimaryProviderComponentName, flags);
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 6d9b51cb..2e1e90c 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -200,6 +200,8 @@
                 supportedCaptureSizes.put(format, supportedSizes);
             }
         }
+
+        int captureFormat = ImageFormat.UNKNOWN;
         Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
                 config.getOutputConfigurations(), supportedCaptureSizes);
         OutputConfiguration burstCaptureOutputConfig = null;
@@ -210,6 +212,12 @@
                 }
             }
             suitableSurfaceCount++;
+
+            if (Flags.analytics24q3()) {
+                CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+                        CameraExtensionUtils.querySurface(burstCaptureSurface);
+                captureFormat = burstCaptureSurfaceInfo.mFormat;
+            }
         }
 
         if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -249,6 +257,9 @@
                 burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
                 config.getExecutor(), sessionId, token, config.getExtension());
 
+        if (Flags.analytics24q3()) {
+            ret.mStatsAggregator.setCaptureFormat(captureFormat);
+        }
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 3ae3199..a4ae398 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -200,10 +200,18 @@
                 supportedCaptureSizes.put(format, supportedSizes);
             }
         }
+
+        int captureFormat = ImageFormat.UNKNOWN;
         Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
                 config.getOutputConfigurations(), supportedCaptureSizes);
         if (burstCaptureSurface != null) {
             suitableSurfaceCount++;
+
+            if (Flags.analytics24q3()) {
+                CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+                        CameraExtensionUtils.querySurface(burstCaptureSurface);
+                captureFormat = burstCaptureSurfaceInfo.mFormat;
+            }
         }
 
         if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -258,6 +266,9 @@
                 extensionChars.getAvailableCaptureResultKeys(config.getExtension()),
                 config.getExtension());
 
+        if (Flags.analytics24q3()) {
+            session.mStatsAggregator.setCaptureFormat(captureFormat);
+        }
         session.mStatsAggregator.setClientName(ctx.getOpPackageName());
         session.mStatsAggregator.setExtensionType(config.getExtension());
 
diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
index 3050a51..c75e418 100644
--- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
+++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
@@ -70,6 +70,23 @@
     }
 
     /**
+     * Set the capture format.
+     *
+     * @param format Format of requested capture.
+     */
+    public void setCaptureFormat(int format) {
+        synchronized (mLock) {
+            if (mIsDone) {
+                return;
+            }
+            if (DEBUG) {
+                Log.v(TAG, "Setting capture format: " + format);
+            }
+            mStats.captureFormat = format;
+        }
+    }
+
+    /**
      * Set extension type.
      *
      * @param extensionType Type of extension. Must match one of
@@ -116,7 +133,8 @@
                 + "  cameraId: '" + stats.cameraId + "'\n"
                 + "  clientName: '" + stats.clientName + "'\n"
                 + "  type: '" + stats.type + "'\n"
-                + "  isAdvanced: '" + stats.isAdvanced + "'\n";
+                + "  isAdvanced: '" + stats.isAdvanced + "'\n"
+                + "  captureFormat: '" + stats.captureFormat + "'\n";
     }
 
     /**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 8f78032..45b316a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -171,9 +171,9 @@
     void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor);
 
     // Add a runtime association between the input device and display, using device's port.
-    void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
+    void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId);
     // Remove the runtime association between the input device and display, using device's port.
-    void removeUniqueIdAssociation(in String inputPort);
+    void removeUniqueIdAssociationByPort(in String inputPort);
 
     InputSensorInfo[] getSensorList(int deviceId);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 57004bc..7527aa7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1094,10 +1094,9 @@
      */
     @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     @TestApi
-    // TODO(b/324075859): Rename to addUniqueIdAssociationByPort
-    public void addUniqueIdAssociation(@NonNull String inputPort,
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
             @NonNull String displayUniqueId) {
-        mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId);
+        mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
     }
 
     /**
@@ -1110,9 +1109,8 @@
      */
     @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     @TestApi
-    // TODO(b/324075859): Rename to removeUniqueIdAssociationByPort
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
-        mGlobal.removeUniqueIdAssociation(inputPort);
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
+        mGlobal.removeUniqueIdAssociationByPort(inputPort);
     }
 
     /**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index cb3af2b..fcd5a3e 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1445,22 +1445,23 @@
     }
 
     /**
-     * @see InputManager#addUniqueIdAssociation(String, String)
+     * @see InputManager#addUniqueIdAssociationByPort(String, String)
      */
-    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+            @NonNull String displayUniqueId) {
         try {
-            mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
+            mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * @see InputManager#removeUniqueIdAssociation(String)
+     * @see InputManager#removeUniqueIdAssociationByPort(String)
      */
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
         try {
-            mIm.removeUniqueIdAssociation(inputPort);
+            mIm.removeUniqueIdAssociationByPort(inputPort);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
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/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 92f2c32..3cd705a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -86,7 +86,8 @@
             @NonNull Context context,
             @NonNull ComponentName serviceComponent,
             int userId,
-            boolean isSystemProvider)
+            boolean isSystemProvider,
+            boolean isPrimary)
             throws PackageManager.NameNotFoundException {
         return create(
                 context,
@@ -94,7 +95,7 @@
                 isSystemProvider,
                 /* disableSystemAppVerificationForTests= */ false,
                 /* isEnabled= */ false,
-                /* isPrimary= */ false);
+                isPrimary);
     }
 
     /**
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/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bfe4e6f..9cc4191 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1978,6 +1978,13 @@
 
                     if (Objects.equals(mLastAutofilledData.get(id), value)) {
                         view.setAutofilled(true, hideHighlight);
+                        try {
+                            mService.setViewAutofilled(mSessionId, id, mContext.getUserId());
+                        } catch (RemoteException e) {
+                            // The failure could be a consequence of something going wrong on the
+                            // server side. Do nothing here since it's just logging, but it's
+                            // possible follow-up actions may fail.
+                        }
                     } else {
                         view.setAutofilled(false, false);
                         mLastAutofilledData.remove(id);
@@ -2978,6 +2985,13 @@
                 mLastAutofilledData.put(view.getAutofillId(), targetValue);
             }
             view.setAutofilled(true, hideHighlight);
+            try {
+                mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId());
+            } catch (RemoteException e) {
+                // The failure could be a consequence of something going wrong on the server side.
+                // Do nothing here since it's just logging, but it's possible follow-up actions may
+                // fail.
+            }
         }
     }
 
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 1a9322e..2039b4d 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -49,6 +49,7 @@
     void updateSession(int sessionId, in AutofillId id, in Rect bounds,
         in AutofillValue value, int action, int flags, int userId);
     void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+    void setViewAutofilled(int sessionId, in AutofillId id, int userId);
     void finishSession(int sessionId, int userId, int commitReason);
     void cancelSession(int sessionId, int userId);
     void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c5114b9..fb3e083 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -720,6 +720,20 @@
 
     private boolean mIsStylusHandwritingEnabled;
 
+
+    /**
+     * AndroidX Core library 1.13.0 introduced EditorInfoCompat#setStylusHandwritingEnabled and
+     * EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the EditorInfo
+     * extras bundle. These methods do not set or check the Android V property since the Android V
+     * SDK was not yet available. In order for EditorInfoCompat#isStylusHandwritingEnabled to return
+     * the correct value for EditorInfo created by Android V TextView, the extras bundle value
+     * should be set. This is the extras bundle key.
+     *
+     * @hide
+     */
+    public static final String STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY =
+            "androidx.core.view.inputmethod.EditorInfoCompat.STYLUS_HANDWRITING_ENABLED";
+
     /**
      * Set {@code true} if the {@code Editor} has
      * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index fa9458d..56e5bcf 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,6 +62,17 @@
 }
 
 flag {
+    name: "use_input_method_info_safe_list"
+    namespace: "input_method"
+    description: "Use InputMethodInfoSafeList for more reliable binder IPCs"
+    bug: "339761278"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "ime_switcher_revamp"
     is_exported: true
     namespace: "input_method"
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fb1c331..78dd3b1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,6 +27,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -10062,9 +10063,22 @@
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
                 outAttrs.setInitialSurroundingText(mText);
                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
-                if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
-                        && isAutoHandwritingEnabled()) {
-                    outAttrs.setStylusHandwritingEnabled(true);
+                if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) {
+                    boolean handwritingEnabled = isAutoHandwritingEnabled();
+                    outAttrs.setStylusHandwritingEnabled(handwritingEnabled);
+                    // AndroidX Core library 1.13.0 introduced
+                    // EditorInfoCompat#setStylusHandwritingEnabled and
+                    // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the
+                    // EditorInfo extras bundle. These methods do not set or check the Android V
+                    // property since the Android V SDK was not yet available. In order for
+                    // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for
+                    // EditorInfo created by Android V TextView, the extras bundle value is also set
+                    // here.
+                    if (outAttrs.extras == null) {
+                        outAttrs.extras = new Bundle();
+                    }
+                    outAttrs.extras.putBoolean(
+                            STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled);
                 }
                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
                 gestures.add(SelectGesture.class);
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/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/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 14388a6..5b0e6b9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -855,7 +855,9 @@
         if (!parentInfo.isVisible()) {
             // Only making the TaskContainer invisible and drops the other info, and perform the
             // update when the next time the Task becomes visible.
-            taskContainer.setIsVisible(false);
+            if (taskContainer.isVisible()) {
+                taskContainer.setInvisible();
+            }
             return;
         }
 
@@ -3228,10 +3230,8 @@
             @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
         final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
         final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
-        if (parentInfo != null) {
-            dividerPresenter.updateDivider(
-                    wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
-        }
+        dividerPresenter.updateDivider(
+                wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index a683738..c708da9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -78,16 +78,7 @@
     private TaskFragmentContainer mAlwaysOnTopOverlayContainer;
 
     @NonNull
-    private final Configuration mConfiguration;
-
-    private int mDisplayId;
-
-    private boolean mIsVisible;
-
-    private boolean mHasDirectActivity;
-
-    @Nullable
-    private TaskFragmentParentInfo mTaskFragmentParentInfo;
+    private TaskFragmentParentInfo mInfo;
 
     /**
      * TaskFragments that the organizer has requested to be closed. They should be removed when
@@ -131,12 +122,14 @@
         mTaskId = taskId;
         final TaskProperties taskProperties = TaskProperties
                 .getTaskPropertiesFromActivity(activityInTask);
-        mConfiguration = taskProperties.getConfiguration();
-        mDisplayId = taskProperties.getDisplayId();
-        // Note that it is always called when there's a new Activity is started, which implies
-        // the host task is visible and has an activity in the task.
-        mIsVisible = true;
-        mHasDirectActivity = true;
+        mInfo = new TaskFragmentParentInfo(
+                taskProperties.getConfiguration(),
+                taskProperties.getDisplayId(),
+                // Note that it is always called when there's a new Activity is started, which
+                // implies the host task is visible and has an activity in the task.
+                true /* visible */,
+                true /* hasDirectActivity */,
+                null /* decorSurface */);
     }
 
     int getTaskId() {
@@ -144,43 +137,39 @@
     }
 
     int getDisplayId() {
-        return mDisplayId;
+        return mInfo.getDisplayId();
     }
 
     boolean isVisible() {
-        return mIsVisible;
+        return mInfo.isVisible();
     }
 
-    void setIsVisible(boolean visible) {
-        mIsVisible = visible;
+    void setInvisible() {
+        mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(),
+                false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface());
     }
 
     boolean hasDirectActivity() {
-        return mHasDirectActivity;
+        return mInfo.hasDirectActivity();
     }
 
     @NonNull
     Rect getBounds() {
-        return mConfiguration.windowConfiguration.getBounds();
+        return mInfo.getConfiguration().windowConfiguration.getBounds();
     }
 
     @NonNull
     TaskProperties getTaskProperties() {
-        return new TaskProperties(mDisplayId, mConfiguration);
+        return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration());
     }
 
     void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
-        // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields.
-        mConfiguration.setTo(info.getConfiguration());
-        mDisplayId = info.getDisplayId();
-        mIsVisible = info.isVisible();
-        mHasDirectActivity = info.hasDirectActivity();
-        mTaskFragmentParentInfo = info;
+        mInfo = info;
     }
 
-    @Nullable
+    @NonNull
     TaskFragmentParentInfo getTaskFragmentParentInfo() {
-        return mTaskFragmentParentInfo;
+        return mInfo;
     }
 
     /**
@@ -196,8 +185,8 @@
 
         // If the task properties equals regardless of starting position, don't
         // need to update the container.
-        return mConfiguration.diffPublicOnly(configuration) != 0
-                || mDisplayId != info.getDisplayId();
+        return mInfo.getConfiguration().diffPublicOnly(configuration) != 0
+                || mInfo.getDisplayId() != info.getDisplayId();
     }
 
     /**
@@ -224,7 +213,7 @@
     }
 
     boolean isInPictureInPicture() {
-        return isInPictureInPicture(mConfiguration);
+        return isInPictureInPicture(mInfo.getConfiguration());
     }
 
     private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
@@ -237,7 +226,7 @@
 
     @WindowingMode
     private int getWindowingMode() {
-        return mConfiguration.windowConfiguration.getWindowingMode();
+        return mInfo.getConfiguration().windowConfiguration.getWindowingMode();
     }
 
     /** Whether there is any {@link TaskFragmentContainer} below this Task. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index ceeed88..ea30af5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -159,6 +159,8 @@
                     bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
                 }
             }
+            bubbleBarUpdate.showOverflowChanged = showOverflowChanged;
+            bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty();
             return bubbleBarUpdate;
         }
 
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..be88b34 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
@@ -536,7 +536,8 @@
     private OnClickListener mBubbleClickListener = new OnClickListener() {
         @Override
         public void onClick(View view) {
-            mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging.
+            // If the touch ended in a click, we're no longer dragging.
+            onDraggingEnded();
 
             // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we
             // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust
@@ -719,8 +720,7 @@
                 mDismissView.hide();
             }
 
-            mIsDraggingStack = false;
-            mMagnetizedObject = null;
+            onDraggingEnded();
 
             // Hide the stack after a delay, if needed.
             updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
@@ -1096,6 +1096,7 @@
             } else {
                 maybeShowStackEdu();
             }
+            onDraggingEnded();
         });
 
         animate()
@@ -1153,6 +1154,14 @@
     }
 
     /**
+     * Reset state related to dragging.
+     */
+    private void onDraggingEnded() {
+        mIsDraggingStack = false;
+        mMagnetizedObject = null;
+    }
+
+    /**
      * Sets whether or not the stack should become temporarily invisible by moving off the side of
      * the screen.
      *
@@ -2341,8 +2350,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/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index 6980c6f..ec3c601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -52,6 +52,8 @@
     public BubbleBarLocation bubbleBarLocation;
     @Nullable
     public Point expandedViewDropTargetSize;
+    public boolean showOverflowChanged;
+    public boolean showOverflow;
 
     // This is only populated if bubbles have been removed.
     public List<RemovedBubble> removedBubbles = new ArrayList<>();
@@ -92,6 +94,8 @@
                 BubbleBarLocation.class);
         expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(),
                 Point.class);
+        showOverflowChanged = parcel.readBoolean();
+        showOverflow = parcel.readBoolean();
     }
 
     /**
@@ -107,7 +111,8 @@
                 || suppressedBubbleKey != null
                 || unsupressedBubbleKey != null
                 || !currentBubbleList.isEmpty()
-                || bubbleBarLocation != null;
+                || bubbleBarLocation != null
+                || showOverflowChanged;
     }
 
     @NonNull
@@ -128,6 +133,8 @@
                 + " currentBubbleList=" + currentBubbleList
                 + " bubbleBarLocation=" + bubbleBarLocation
                 + " expandedViewDropTargetSize=" + expandedViewDropTargetSize
+                + " showOverflowChanged=" + showOverflowChanged
+                + " showOverflow=" + showOverflow
                 + " }";
     }
 
@@ -152,6 +159,8 @@
         parcel.writeParcelableList(currentBubbleList, flags);
         parcel.writeParcelable(bubbleBarLocation, flags);
         parcel.writeParcelable(expandedViewDropTargetSize, flags);
+        parcel.writeBoolean(showOverflowChanged);
+        parcel.writeBoolean(showOverflow);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index ea86c79..dba0a98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -16,10 +16,12 @@
 package com.android.wm.shell.common.pip
 
 import android.app.ActivityTaskManager
+import android.app.AppGlobals
 import android.app.RemoteAction
 import android.app.WindowConfiguration
 import android.content.ComponentName
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.RemoteException
 import android.os.SystemProperties
 import android.util.DisplayMetrics
@@ -136,8 +138,23 @@
         }
     }
 
+    private var isPip2ExperimentEnabled: Boolean? = null
+
+    /**
+     * Returns true if PiP2 implementation should be used. Besides the trunk stable flag,
+     * system property can be used to override this read only flag during development.
+     * It's currently limited to phone form factor, i.e., not enabled on ARC / TV.
+     */
     @JvmStatic
-    val isPip2ExperimentEnabled: Boolean
-        get() = Flags.enablePip2Implementation() || SystemProperties.getBoolean(
-                "wm_shell.pip2", false)
+    fun isPip2ExperimentEnabled(): Boolean {
+        if (isPip2ExperimentEnabled == null) {
+            val isArc = AppGlobals.getPackageManager().hasSystemFeature(
+                "org.chromium.arc", 0)
+            val isTv = AppGlobals.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK, 0)
+            isPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) ||
+                    (Flags.enablePip2Implementation() && !isArc && !isTv)
+        }
+        return isPip2ExperimentEnabled as Boolean
+    }
 }
\ No newline at end of file
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/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/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 07f0c39..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,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
+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;
@@ -53,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;
 
@@ -398,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;
                 }
@@ -498,6 +505,8 @@
             // 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) {
+                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/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/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 1a5b938..31c9db7 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -23,17 +23,42 @@
 
 #include "GraphicsJNI.h"
 #include "SkColorFilter.h"
-#include "SkiaWrapper.h"
 
 namespace android {
 namespace uirenderer {
 
-class ColorFilter : public SkiaWrapper<SkColorFilter> {
+class ColorFilter : public VirtualLightRefBase {
 public:
     static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
 
+    sk_sp<SkColorFilter> getInstance() {
+        if (mInstance != nullptr && shouldDiscardInstance()) {
+            mInstance = nullptr;
+        }
+
+        if (mInstance == nullptr) {
+            mInstance = createInstance();
+            if (mInstance) {
+                mInstance = mInstance->makeWithWorkingColorSpace(SkColorSpace::MakeSRGB());
+            }
+            mGenerationId++;
+        }
+        return mInstance;
+    }
+
+    virtual bool shouldDiscardInstance() const { return false; }
+
+    void discardInstance() { mInstance = nullptr; }
+
+    [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
 protected:
     ColorFilter() = default;
+    virtual sk_sp<SkColorFilter> createInstance() = 0;
+
+private:
+    sk_sp<SkColorFilter> mInstance = nullptr;
+    int32_t mGenerationId = 0;
 };
 
 class BlendModeColorFilter : public ColorFilter {
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
deleted file mode 100644
index bd0e35a..0000000
--- a/libs/hwui/SkiaWrapper.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SKIA_WRAPPER_H_
-#define SKIA_WRAPPER_H_
-
-#include <SkRefCnt.h>
-#include <utils/RefBase.h>
-
-namespace android::uirenderer {
-
-template <typename T>
-class SkiaWrapper : public VirtualLightRefBase {
-public:
-    sk_sp<T> getInstance() {
-        if (mInstance != nullptr && shouldDiscardInstance()) {
-            mInstance = nullptr;
-        }
-
-        if (mInstance == nullptr) {
-            mInstance = createInstance();
-            mGenerationId++;
-        }
-        return mInstance;
-    }
-
-    virtual bool shouldDiscardInstance() const { return false; }
-
-    void discardInstance() { mInstance = nullptr; }
-
-    [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
-
-protected:
-    virtual sk_sp<T> createInstance() = 0;
-
-private:
-    sk_sp<T> mInstance = nullptr;
-    int32_t mGenerationId = 0;
-};
-
-}  // namespace android::uirenderer
-
-#endif  // SKIA_WRAPPER_H_
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/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 50ebdd5..0da32bd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent
 import android.app.assist.AssistStructure
+import android.content.ComponentName
 import android.content.Context
 import android.credentials.CredentialManager
 import android.credentials.GetCredentialRequest
@@ -34,6 +35,10 @@
 import android.os.OutcomeReceiver
 import android.os.ResultReceiver
 import android.service.autofill.AutofillService
+import com.android.credentialmanager.model.get.ProviderInfo
+import androidx.core.graphics.drawable.toBitmap
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.EntryInfo
 import android.service.autofill.Dataset
 import android.service.autofill.Field
 import android.service.autofill.FillCallback
@@ -63,7 +68,8 @@
 import com.android.credentialmanager.getflow.toProviderDisplayInfo
 import com.android.credentialmanager.ktx.credentialEntry
 import com.android.credentialmanager.model.CredentialType
-import com.android.credentialmanager.model.get.CredentialEntryInfo
+import java.util.ArrayList
+import java.util.Objects
 import java.util.concurrent.Executors
 import org.json.JSONException
 import org.json.JSONObject
@@ -121,8 +127,11 @@
 
         val responseClientState = Bundle()
         responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
-        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-                requestId, resultReceiver, responseClientState)
+        val uniqueAutofillIdsForRequest: MutableSet<AutofillId> = mutableSetOf()
+        val getCredRequest: GetCredentialRequest? = getCredManRequest(
+            structure, sessionId,
+            requestId, resultReceiver, responseClientState, uniqueAutofillIdsForRequest
+        )
         // TODO(b/324635774): Use callback for validating. If the request is coming
         // directly from the view, there should be a corresponding callback, otherwise
         // we should fail fast,
@@ -132,14 +141,17 @@
             return
         }
         val credentialManager: CredentialManager =
-                getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
+            getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
 
         val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
                 GetCandidateCredentialsException> {
             override fun onResult(result: GetCandidateCredentialsResponse) {
                 Log.i(TAG, "getCandidateCredentials onResult")
-                val fillResponse = convertToFillResponse(result, request,
-                    responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest))
+                val fillResponse = convertToFillResponse(
+                    result, request,
+                    responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest),
+                    uniqueAutofillIdsForRequest
+                )
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -195,58 +207,131 @@
 
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
-            filLRequest: FillRequest,
+            fillRequest: FillRequest,
             responseClientState: Bundle,
             typePriorityMap: Map<String, Int>,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ): FillResponse? {
         val candidateProviders = getCredResponse.candidateProviderDataList
         if (candidateProviders.isEmpty()) {
             return null
         }
-
+        val primaryProviderComponentName = getCredResponse.primaryProviderComponentName
         val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
         val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
-                mapAutofillIdToProviders(candidateProviders)
+            mapAutofillIdToProviders(
+                uniqueAutofillIdsForRequest,
+                candidateProviders,
+                primaryProviderComponentName
+            )
         val fillResponseBuilder = FillResponse.Builder()
         fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE)
-        var validFillResponse = false
         autofillIdToProvidersMap.forEach { (autofillId, providers) ->
-            validFillResponse = processProvidersForAutofillId(
-                    filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
-                    getCredResponse.intent, typePriorityMap)
-                    .or(validFillResponse)
+            var credentialDatasetAdded = addCredentialDatasetsForAutofillId(fillRequest,
+                autofillId, providers, entryIconMap, fillResponseBuilder, typePriorityMap)
+            if (!credentialDatasetAdded && primaryProviderComponentName != null) {
+                val providerList = GetFlowUtils.toProviderList(
+                    providers,
+                    this@CredentialAutofillService
+                )
+                val primaryProviderInfo =
+                    providerList.find { provider -> primaryProviderComponentName
+                        .flattenToString().equals(provider.id) }
+                if (primaryProviderInfo != null) {
+                    addActionDatasetsForAutofillId(
+                        fillRequest,
+                        autofillId,
+                        primaryProviderInfo,
+                        fillResponseBuilder
+                    )
+                }
+            }
         }
-        if (!validFillResponse) {
-            return null
+        for (autofillId in uniqueAutofillIdsForRequest) {
+            addMoreOptionsDataset(
+                fillRequest,
+                autofillId,
+                fillResponseBuilder,
+                getCredResponse.intent.putExtra(
+                    ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, ArrayList(candidateProviders)
+                )
+            )
         }
         fillResponseBuilder.setClientState(responseClientState)
         return fillResponseBuilder.build()
     }
 
-    private fun processProvidersForAutofillId(
-            filLRequest: FillRequest,
-            autofillId: AutofillId,
-            providerDataList: ArrayList<GetCredentialProviderData>,
-            entryIconMap: Map<String, Icon>,
-            fillResponseBuilder: FillResponse.Builder,
-            bottomSheetIntent: Intent,
-            typePriorityMap: Map<String, Int>,
+    private fun addActionDatasetsForAutofillId(
+        fillRequest: FillRequest,
+        autofillId: AutofillId,
+        primaryProvider: ProviderInfo,
+        fillResponseBuilder: FillResponse.Builder,
+    ): Boolean {
+        var index = 0
+        var datasetAdded = false
+        primaryProvider.actionEntryList.forEach { actionEntry ->
+            if (index >= maxDatasetDisplayLimit(primaryProvider.actionEntryList.size)) {
+                return@forEach
+            }
+            val pendingIntent = actionEntry.pendingIntent
+            if (pendingIntent == null) {
+                Log.e(TAG, "Pending intent for action chip is null")
+                return@forEach
+            }
+
+            val icon: Icon? = Icon.createWithBitmap(actionEntry.icon.toBitmap())
+            if (icon == null) {
+                Log.e(TAG, "Icon for action chip is null")
+                return@forEach
+            }
+
+            val presentations = constructPresentations(
+                fillRequest,
+                index,
+                actionEntry,
+                pendingIntent,
+                icon,
+                actionEntry.title,
+                actionEntry.subTitle,
+                primaryProvider.actionEntryList.size
+            )
+
+            fillResponseBuilder.addDataset(
+                Dataset.Builder()
+                    .setField(
+                        autofillId,
+                        Field.Builder().setPresentations(presentations).build()
+                    )
+                    .setAuthentication(pendingIntent.intentSender)
+                    .build()
+            )
+            datasetAdded = true
+
+            index++
+        }
+
+        return datasetAdded
+    }
+
+    private fun addCredentialDatasetsForAutofillId(
+        fillRequest: FillRequest,
+        autofillId: AutofillId,
+        providerDataList: List<GetCredentialProviderData>,
+        entryIconMap: Map<String, Icon>,
+        fillResponseBuilder: FillResponse.Builder,
+        typePriorityMap: Map<String, Int>,
     ): Boolean {
         val providerList = GetFlowUtils.toProviderList(
             providerDataList,
-            this@CredentialAutofillService)
+            this@CredentialAutofillService
+        )
         if (providerList.isEmpty()) {
             return false
         }
         val providerDisplayInfo: ProviderDisplayInfo =
-                toProviderDisplayInfo(providerList, typePriorityMap)
+            toProviderDisplayInfo(providerList, typePriorityMap)
         var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size
-        val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
-        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
-        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
-        val maxDatasetDisplayLimit = this.resources.getInteger(
-                com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
-                .coerceAtMost(totalEntryCount)
+
         var i = 0
         var datasetAdded = false
 
@@ -260,8 +345,6 @@
                 }
             }
         }
-        bottomSheetIntent.putExtra(
-                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
             val primaryEntry = it.sortedCredentialEntryList.first()
             val pendingIntent = primaryEntry.pendingIntent
@@ -271,7 +354,7 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (i >= maxDatasetDisplayLimit) {
+            if (i >= maxDatasetDisplayLimit(totalEntryCount)) {
                 return@usernameLoop
             }
             val icon: Icon = if (primaryEntry.icon == null) {
@@ -280,116 +363,172 @@
                 getDefaultIcon()
             } else {
                 entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
-                        ?: getDefaultIcon()
+                    ?: getDefaultIcon()
             }
-            // Create inline presentation
-            var inlinePresentation: InlinePresentation? = null
-            if (inlinePresentationSpecs != null && i < maxDatasetDisplayLimit) {
-                val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
-                    inlinePresentationSpecs[i]
-                } else {
-                    inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
-                }
-                if (spec != null) {
-                    inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
-                            InlinePresentationsFactory.modifyInlinePresentationSpec
-                            (this@CredentialAutofillService, spec),
-                            duplicateDisplayNamesForPasskeys)
-                }
+            val displayName = primaryEntry.displayName
+            val title: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+                displayName != null
+            ) {
+                displayName
+            } else {
+                primaryEntry.userName
             }
-            var dropdownPresentation: RemoteViews? = null
-            if (i < maxDatasetDisplayLimit) {
-                dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
-                    this, icon, primaryEntry, /*isFirstEntry= */ i == 0,
-                    /*isLastEntry= */ (totalEntryCount - i == 1))
+            val subtitle = if (primaryEntry.credentialType ==
+                CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[title] == true
+            ) {
+                primaryEntry.userName
+            } else {
+                null
             }
-
-            val dataSetBuilder = Dataset.Builder()
-            val presentationBuilder = Presentations.Builder()
-            if (dropdownPresentation != null) {
-                presentationBuilder.setMenuPresentation(dropdownPresentation)
-            }
-            if (inlinePresentation != null) {
-                presentationBuilder.setInlinePresentation(inlinePresentation)
-            }
-
+            val presentations =
+                constructPresentations(
+                    fillRequest, i, primaryEntry, pendingIntent,
+                    icon, title, subtitle, totalEntryCount
+                )
             fillResponseBuilder.addDataset(
-                    dataSetBuilder
-                            .setField(
-                                    autofillId,
-                                    Field.Builder().setPresentations(
-                                            presentationBuilder.build())
-                                            .build())
-                            .setAuthentication(pendingIntent.intentSender)
-                            .setCredentialFillInIntent(fillInIntent)
-                            .build())
+                Dataset.Builder()
+                    .setField(
+                        autofillId,
+                        Field.Builder().setPresentations(
+                            presentations
+                        )
+                            .build()
+                    )
+                    .setAuthentication(pendingIntent.intentSender)
+                    .setCredentialFillInIntent(fillInIntent)
+                    .build()
+            )
             datasetAdded = true
             i++
         }
-        val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
-                inlinePresentationSpecsCount)
-        if (datasetAdded) {
-            addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
-            if (pinnedSpec != null) {
-                addPinnedInlineSuggestion(pinnedSpec, autofillId,
-                        fillResponseBuilder, bottomSheetIntent)
-            }
-        }
         return datasetAdded
     }
 
-    private fun createInlinePresentation(
-            primaryEntry: CredentialEntryInfo,
-            pendingIntent: PendingIntent,
-            icon: Icon,
-            spec: InlinePresentationSpec,
-            duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
-    ): InlinePresentation {
-        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
-            primaryEntry.displayName != null) {
-            primaryEntry.displayName!!
-        } else {
-            primaryEntry.userName
+    private fun addMoreOptionsDataset(
+        fillRequest: FillRequest,
+        autofillId: AutofillId,
+        fillResponseBuilder: FillResponse.Builder,
+        bottomSheetIntent: Intent
+    ) {
+        val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
+        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+        val pinnedSpec = getLastInlinePresentationSpec(
+            inlinePresentationSpecs,
+            inlinePresentationSpecsCount
+        )
+        addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
+        if (pinnedSpec != null) {
+            addPinnedInlineSuggestion(
+                pinnedSpec, autofillId,
+                fillResponseBuilder, bottomSheetIntent
+            )
         }
+    }
+
+    private fun constructPresentations(
+        fillRequest: FillRequest,
+        index: Int,
+        entry: EntryInfo,
+        pendingIntent: PendingIntent,
+        icon: Icon,
+        title: String,
+        subtitle: String?,
+        totalEntryCount: Int
+    ): Presentations {
+        val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
+        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+
+        // Create inline presentation
+        var inlinePresentation: InlinePresentation? = null
+        if (inlinePresentationSpecs != null && index < maxDatasetDisplayLimit(totalEntryCount)) {
+            val spec: InlinePresentationSpec? = if (index < inlinePresentationSpecsCount) {
+                inlinePresentationSpecs[index]
+            } else {
+                inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+            }
+            if (spec != null) {
+                inlinePresentation = createInlinePresentation(
+                    pendingIntent, icon,
+                    InlinePresentationsFactory.modifyInlinePresentationSpec
+                        (this@CredentialAutofillService, spec),
+                    title, subtitle, entry is ActionEntryInfo
+                )
+            }
+        }
+        var dropdownPresentation: RemoteViews? = null
+        if (index < maxDatasetDisplayLimit(totalEntryCount)) {
+            dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+                this, icon, entry, /*isFirstEntry= */ index == 0,
+                /*isLastEntry= */ (totalEntryCount - index == 1)
+            )
+        }
+
+        val presentationBuilder = Presentations.Builder()
+        if (dropdownPresentation != null) {
+            presentationBuilder.setMenuPresentation(dropdownPresentation)
+        }
+        if (inlinePresentation != null) {
+            presentationBuilder.setInlinePresentation(inlinePresentation)
+        }
+        return presentationBuilder.build()
+    }
+
+    private fun maxDatasetDisplayLimit(totalEntryCount: Int) = this.resources.getInteger(
+        com.android.credentialmanager.R.integer.autofill_max_visible_datasets
+    ).coerceAtMost(totalEntryCount)
+
+    private fun createInlinePresentation(
+        pendingIntent: PendingIntent,
+        icon: Icon,
+        spec: InlinePresentationSpec,
+        title: String,
+        subtitle: String?,
+        isActionEntry: Boolean
+    ): InlinePresentation {
         val sliceBuilder = InlineSuggestionUi
-                .newContentBuilder(pendingIntent)
-                .setTitle(displayName)
+            .newContentBuilder(pendingIntent)
+            .setTitle(title)
         icon.setTintBlendMode(BlendMode.DST)
         sliceBuilder.setStartIcon(icon)
-        if (primaryEntry.credentialType ==
-                CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
-            sliceBuilder.setSubtitle(primaryEntry.userName)
+        if (subtitle != null && !isActionEntry) {
+            sliceBuilder.setSubtitle(subtitle)
         }
         return InlinePresentation(
-                sliceBuilder.build().slice, spec, /* pinned= */ false)
+            sliceBuilder.build().slice, spec, /* pinned= */ false
+        )
     }
 
     private fun addDropdownMoreOptionsPresentation(
-            bottomSheetIntent: Intent,
-            autofillId: AutofillId,
-            fillResponseBuilder: FillResponse.Builder
+        bottomSheetIntent: Intent,
+        autofillId: AutofillId,
+        fillResponseBuilder: FillResponse.Builder
     ) {
         val presentationBuilder = Presentations.Builder()
-                .setMenuPresentation(
-                        RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+            .setMenuPresentation(
+                RemoteViewsFactory.createMoreSignInOptionsPresentation(this)
+            )
         val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
 
         fillResponseBuilder.addDataset(
-                Dataset.Builder()
-                        .setId(AutofillManager.PINNED_DATASET_ID)
-                        .setField(
-                                autofillId,
-                                Field.Builder().setPresentations(
-                                        presentationBuilder.build())
-                                        .build())
-                        .setAuthentication(pendingIntent.intentSender)
+            Dataset.Builder()
+                .setId(AutofillManager.PINNED_DATASET_ID)
+                .setField(
+                    autofillId,
+                    Field.Builder().setPresentations(
+                        presentationBuilder.build()
+                    )
                         .build()
+                )
+                .setAuthentication(pendingIntent.intentSender)
+                .build()
         )
     }
 
     private fun getLastInlinePresentationSpec(
-            inlinePresentationSpecs: List<InlinePresentationSpec>?,
-            inlinePresentationSpecsCount: Int
+        inlinePresentationSpecs: List<InlinePresentationSpec>?,
+        inlinePresentationSpecsCount: Int
     ): InlinePresentationSpec? {
         if (inlinePresentationSpecs != null) {
             return inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
@@ -398,40 +537,47 @@
     }
 
     private fun addPinnedInlineSuggestion(
-            spec: InlinePresentationSpec,
-            autofillId: AutofillId,
-            fillResponseBuilder: FillResponse.Builder,
-            bottomSheetIntent: Intent
+        spec: InlinePresentationSpec,
+        autofillId: AutofillId,
+        fillResponseBuilder: FillResponse.Builder,
+        bottomSheetIntent: Intent
     ) {
         val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
 
         val dataSetBuilder = Dataset.Builder()
         val sliceBuilder = InlineSuggestionUi
-                .newContentBuilder(pendingIntent)
-                .setStartIcon(Icon.createWithResource(this,
-                        com.android.credentialmanager.R.drawable.more_horiz_24px))
+            .newContentBuilder(pendingIntent)
+            .setStartIcon(
+                Icon.createWithResource(
+                    this,
+                    com.android.credentialmanager.R.drawable.more_horiz_24px
+                )
+            )
         val presentationBuilder = Presentations.Builder()
-                .setInlinePresentation(InlinePresentation(
-                        sliceBuilder.build().slice, spec, /* pinned= */ true))
+            .setInlinePresentation(
+                InlinePresentation(
+                    sliceBuilder.build().slice, spec, /* pinned= */ true
+                )
+            )
 
         fillResponseBuilder.addDataset(
-                dataSetBuilder
-                        .setId(AutofillManager.PINNED_DATASET_ID)
-                        .setField(
-                                autofillId,
-                                Field.Builder().setPresentations(
-                                        presentationBuilder.build()
-                                ).build()
-                        )
-                        .setAuthentication(pendingIntent.intentSender)
-                        .build()
+            dataSetBuilder
+                .setId(AutofillManager.PINNED_DATASET_ID)
+                .setField(
+                    autofillId,
+                    Field.Builder().setPresentations(
+                        presentationBuilder.build()
+                    ).build()
+                )
+                .setAuthentication(pendingIntent.intentSender)
+                .build()
         )
     }
 
     private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent {
         intent.setAction(java.util.UUID.randomUUID().toString())
         return PendingIntent.getActivity(this, /*requestCode=*/0, intent,
-                PendingIntent.FLAG_MUTABLE, /*options=*/null)
+            PendingIntent.FLAG_MUTABLE, /*options=*/null)
     }
 
     /**
@@ -465,17 +611,33 @@
      *     }
      */
     private fun mapAutofillIdToProviders(
-        providerList: List<GetCredentialProviderData>
+        uniqueAutofillIdsForRequest: Set<AutofillId>,
+        providerList: List<GetCredentialProviderData>,
+        primaryProviderComponentName: ComponentName?
     ): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
         val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
             mutableMapOf()
+        var primaryProvider: GetCredentialProviderData? = null
         providerList.forEach { provider ->
+            if (primaryProviderComponentName != null && Objects.equals(ComponentName
+                .unflattenFromString(provider
+                    .providerFlattenedComponentName), primaryProviderComponentName)) {
+                primaryProvider = provider
+            }
             val autofillIdToCredentialEntries:
                     MutableMap<AutofillId, ArrayList<Entry>> =
                 mapAutofillIdToCredentialEntries(provider.credentialEntries)
             autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
                 autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
-                        .add(copyProviderInfo(provider, entries))
+                    .add(copyProviderInfo(provider, entries))
+            }
+        }
+        // adds primary provider action entries for autofill IDs without credential entries
+        uniqueAutofillIdsForRequest.forEach { autofillId ->
+            if (!autofillIdToProviders.containsKey(autofillId) && primaryProvider != null) {
+                autofillIdToProviders.put(
+                    autofillId,
+                    ArrayList(listOf(copyProviderInfoForActionsOnly(primaryProvider!!))))
             }
         }
         return autofillIdToProviders
@@ -526,19 +688,35 @@
         )
     }
 
+    private fun copyProviderInfoForActionsOnly(
+        providerInfo: GetCredentialProviderData,
+    ): GetCredentialProviderData {
+        return GetCredentialProviderData(
+            providerInfo.providerFlattenedComponentName,
+            emptyList(),
+            providerInfo.actionChips,
+            emptyList(),
+            null
+        )
+    }
+
     override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
         TODO("Not yet implemented")
     }
 
     private fun getCredManRequest(
-            structure: AssistStructure,
-            sessionId: Int,
-            requestId: Int,
-            resultReceiver: ResultReceiver,
-            responseClientState: Bundle
+        structure: AssistStructure,
+        sessionId: Int,
+        requestId: Int,
+        resultReceiver: ResultReceiver,
+        responseClientState: Bundle,
+        uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId)
+        traverseStructureForRequest(
+            structure, credentialOptions, responseClientState,
+            sessionId, uniqueAutofillIdsForRequest
+        )
 
         if (credentialOptions.isNotEmpty()) {
             val dataBundle = Bundle()
@@ -558,7 +736,8 @@
             structure: AssistStructure,
             cmRequests: MutableList<CredentialOption>,
             responseClientState: Bundle,
-            sessionId: Int
+            sessionId: Int,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ) {
         val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf()
         val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf()
@@ -570,7 +749,7 @@
         windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
             traverseNodeForRequest(
                 windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
-                credentialOptionsFromHints, sessionId)
+                credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest)
         }
     }
 
@@ -580,7 +759,8 @@
             responseClientState: Bundle,
             traversedViewNodes: MutableSet<AutofillId>,
             credentialOptionsFromHints: MutableMap<String, CredentialOption>,
-            sessionId: Int
+            sessionId: Int,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ) {
         viewNode.autofillId?.let {
             val domain = viewNode.webDomain
@@ -590,7 +770,9 @@
                     WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
             }
             cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
-                traversedViewNodes, credentialOptionsFromHints, sessionId))
+                traversedViewNodes, credentialOptionsFromHints, sessionId,
+                uniqueAutofillIdsForRequest)
+            )
             traversedViewNodes.add(it)
         }
 
@@ -600,8 +782,10 @@
                 }
 
         children.forEach { childNode: AssistStructure.ViewNode ->
-            traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
-                credentialOptionsFromHints, sessionId)
+            traverseNodeForRequest(
+                childNode, cmRequests, responseClientState, traversedViewNodes,
+                credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest
+            )
         }
     }
 
@@ -611,7 +795,8 @@
             responseClientState: Bundle,
             traversedViewNodes: MutableSet<AutofillId>,
             credentialOptionsFromHints: MutableMap<String, CredentialOption>,
-            sessionId: Int
+            sessionId: Int,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ): MutableList<CredentialOption> {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
@@ -641,8 +826,9 @@
                                 CredentialProviderService.EXTRA_AUTOFILL_ID,
                                 associatedAutofillIds
                             )
+                            uniqueAutofillIdsForRequest.addAll(associatedAutofillIds)
                         }
-            }
+                }
         }
         // TODO(b/325502552): clean up cred option logic in autofill hint
         val credentialHints: MutableList<String> = mutableListOf()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 7bb08d2..98e1690 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -21,8 +21,9 @@
 import com.android.credentialmanager.common.Constants
 import android.widget.RemoteViews
 import androidx.core.content.ContextCompat
+import com.android.credentialmanager.model.get.ActionEntryInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
 import android.graphics.drawable.Icon
 
 class RemoteViewsFactory {
@@ -39,37 +40,47 @@
         fun createDropdownPresentation(
             context: Context,
             icon: Icon,
-            credentialEntryInfo: CredentialEntryInfo,
+            entryInfo: EntryInfo,
             isFirstEntry: Boolean,
             isLastEntry: Boolean,
         ): RemoteViews {
             var layoutId: Int = com.android.credentialmanager.R.layout
                     .credman_dropdown_presentation_layout
             val remoteViews = RemoteViews(context.packageName, layoutId)
-            val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
-            remoteViews.setTextViewText(android.R.id.text1, displayName)
-            val secondaryText = getSecondaryText(credentialEntryInfo)
-            if (secondaryText.isNullOrBlank()) {
-                Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null")
-            } else {
-                remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+            if (entryInfo is CredentialEntryInfo) {
+                val displayName = entryInfo.displayName ?: entryInfo.userName
+                remoteViews.setTextViewText(android.R.id.text1, displayName)
+                val secondaryText = getSecondaryText(entryInfo)
+                if (secondaryText.isNullOrBlank()) {
+                    Log.w(Constants.LOG_TAG, "Secondary text for dropdown credential entry is null")
+                } else {
+                    remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+                }
+                remoteViews.setContentDescription(
+                    android.R.id.icon1, entryInfo
+                        .providerDisplayName
+                )
+            } else if (entryInfo is ActionEntryInfo) {
+                remoteViews.setTextViewText(android.R.id.text1, entryInfo.title)
+                remoteViews.setTextViewText(android.R.id.text2, entryInfo.subTitle)
             }
-            remoteViews.setImageViewIcon(android.R.id.icon1, icon);
+            remoteViews.setImageViewIcon(android.R.id.icon1, icon)
             remoteViews.setBoolean(
-                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
+                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true
+            )
             remoteViews.setInt(
                 android.R.id.icon1,
                 SET_MAX_HEIGHT_METHOD_NAME,
                 context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_icon_size));
-            remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
-                    .providerDisplayName);
+                    com.android.credentialmanager.R.dimen.autofill_icon_size
+                )
+            )
             val drawableId =
                 if (isFirstEntry)
                     com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else
                     com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle
             remoteViews.setInt(
-                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
+                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId)
             if (isFirstEntry) remoteViews.setViewPadding(
                 com.android.credentialmanager.R.id.credential_card,
                 /* left=*/0,
@@ -94,8 +105,8 @@
          * providerDisplayName. Both credential type and provider display name should not be empty.
          */
         private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? {
-            return listOf(if (credentialEntryInfo.displayName != null
-                && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
+            return listOf(if (credentialEntryInfo.displayName != null &&
+                (credentialEntryInfo.displayName != credentialEntryInfo.userName))
                 (credentialEntryInfo.userName) else null,
                 credentialEntryInfo.credentialTypeDisplayName,
                 credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() }
@@ -113,16 +124,16 @@
                 com.android.credentialmanager
                         .R.string.dropdown_presentation_more_sign_in_options_text))
             remoteViews.setBoolean(
-                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
+                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true)
             remoteViews.setInt(
                 android.R.id.icon1,
                 SET_MAX_HEIGHT_METHOD_NAME,
                 context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_icon_size));
+                    com.android.credentialmanager.R.dimen.autofill_icon_size))
             val drawableId =
                 com.android.credentialmanager.R.drawable.more_options_list_item
             remoteViews.setInt(
-                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
+                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId)
             return remoteViews
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 8e78861..19f5a99 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -292,12 +292,12 @@
         providerDisplayInfo.remoteEntry == null &&
         providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty })
         GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY
+    else if (isRequestForAllOptions)
+        GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
     else if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
         providerDisplayInfo.authenticationEntryList.isEmpty() &&
         providerDisplayInfo.remoteEntry != null)
         GetScreenState.REMOTE_ONLY
-    else if (isRequestForAllOptions)
-        GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
     else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
         GetScreenState.BIOMETRIC_SELECTION
     else GetScreenState.PRIMARY_SELECTION
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 14ebc39..755fe2a 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -4,6 +4,16 @@
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
+    name: "delay_show_magnification_button"
+    namespace: "accessibility"
+    description: "Delays the showing of magnification mode switch button."
+    bug: "338259519"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "floating_menu_animated_tuck"
     namespace: "accessibility"
     description: "Sets up animations for tucking/untucking and adjusts clipbounds."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 80398cd..e69ac0a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -857,6 +857,16 @@
 }
 
 flag {
+   name: "restart_dream_on_unocclude"
+   namespace: "systemui"
+   description: "re-enters dreaming upon unocclude when dreaming when originally occluding"
+   bug: "338051457"
+   metadata {
+     purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
   name: "communal_bouncer_do_not_modify_plugin_open"
   namespace: "systemui"
   description: "do not modify notification shade when handling bouncer expansion."
@@ -865,3 +875,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "app_clips_backlinks"
+  namespace: "systemui"
+  description: "Enables Backlinks improvement feature in App Clips"
+  bug: "300307759"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3227611..6fe5cef 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -59,7 +59,8 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.outlined.Edit
 import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.material.icons.outlined.Widgets
@@ -277,7 +278,6 @@
 
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
             Toolbar(
-                isDraggingToRemove = isDraggingToRemove,
                 setToolbarSize = { toolbarSize = it },
                 setRemoveButtonCoordinates = { removeButtonCoordinates = it },
                 onEditDone = onEditDone,
@@ -577,7 +577,6 @@
  */
 @Composable
 private fun Toolbar(
-    isDraggingToRemove: Boolean,
     removeEnabled: Boolean,
     onRemoveClicked: () -> Unit,
     setToolbarSize: (toolbarSize: IntSize) -> Unit,
@@ -591,7 +590,7 @@
             label = "RemoveButtonAlphaAnimation"
         )
 
-    Row(
+    Box(
         modifier =
             Modifier.fillMaxWidth()
                 .padding(
@@ -600,65 +599,54 @@
                     end = Dimensions.ToolbarPaddingHorizontal,
                 )
                 .onSizeChanged { setToolbarSize(it) },
-        horizontalArrangement = Arrangement.SpaceBetween,
-        verticalAlignment = Alignment.CenterVertically
     ) {
         val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing)
-        Button(
-            onClick = onOpenWidgetPicker,
-            colors = filledButtonColors(),
-            contentPadding = Dimensions.ButtonPadding
-        ) {
-            Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
-            Spacer(spacerModifier)
-            Text(
-                text = stringResource(R.string.hub_mode_add_widget_button_text),
-            )
+
+        if (!removeEnabled) {
+            Button(
+                modifier = Modifier.align(Alignment.CenterStart),
+                onClick = onOpenWidgetPicker,
+                colors = filledButtonColors(),
+                contentPadding = Dimensions.ButtonPadding
+            ) {
+                Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
+                Spacer(spacerModifier)
+                Text(
+                    text = stringResource(R.string.hub_mode_add_widget_button_text),
+                )
+            }
         }
 
-        val colors = LocalAndroidColorScheme.current
-        if (isDraggingToRemove) {
+        if (removeEnabled) {
             Button(
-                // Button is disabled to make it non-clickable
-                enabled = false,
-                onClick = {},
-                colors =
-                    ButtonDefaults.buttonColors(
-                        disabledContainerColor = colors.primary,
-                        disabledContentColor = colors.onPrimary,
-                    ),
-                contentPadding = Dimensions.ButtonPadding,
-                modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
-            ) {
-                RemoveButtonContent(spacerModifier)
-            }
-        } else {
-            OutlinedButton(
-                enabled = removeEnabled,
                 onClick = onRemoveClicked,
-                colors =
-                    ButtonDefaults.outlinedButtonColors(
-                        contentColor = colors.primary,
-                        disabledContentColor = colors.primary
-                    ),
-                border = BorderStroke(width = 1.0.dp, color = colors.primary),
+                colors = filledButtonColors(),
                 contentPadding = Dimensions.ButtonPadding,
                 modifier =
                     Modifier.graphicsLayer { alpha = removeButtonAlpha }
                         .onGloballyPositioned { setRemoveButtonCoordinates(it) }
+                        .align(Alignment.Center)
             ) {
                 RemoveButtonContent(spacerModifier)
             }
         }
 
-        Button(
-            onClick = onEditDone,
-            colors = filledButtonColors(),
-            contentPadding = Dimensions.ButtonPadding
-        ) {
-            Text(
-                text = stringResource(R.string.hub_mode_editing_exit_button_text),
-            )
+        if (!removeEnabled) {
+            Button(
+                modifier = Modifier.align(Alignment.CenterEnd),
+                onClick = onEditDone,
+                colors = filledButtonColors(),
+                contentPadding = Dimensions.ButtonPadding
+            ) {
+                Icon(
+                    Icons.Default.Check,
+                    stringResource(id = R.string.hub_mode_editing_exit_button_text)
+                )
+                Spacer(spacerModifier)
+                Text(
+                    text = stringResource(R.string.hub_mode_editing_exit_button_text),
+                )
+            }
         }
     }
 }
@@ -762,7 +750,7 @@
 
 @Composable
 private fun RemoveButtonContent(spacerModifier: Modifier) {
-    Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget))
+    Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget))
     Spacer(spacerModifier)
     Text(
         text = stringResource(R.string.button_to_remove_widget),
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/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 92d5c26..d924d88 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -74,16 +74,6 @@
          */
         val isUserInputOngoing: Flow<Boolean>,
     ) : ObservableTransitionState
-
-    fun isIdle(scene: SceneKey?): Boolean {
-        return this is Idle && (scene == null || this.currentScene == scene)
-    }
-
-    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
-        return this is Transition &&
-            (from == null || this.fromScene == from) &&
-            (to == null || this.toScene == to)
-    }
 }
 
 /**
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/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index def63ec..bfed33c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,12 +16,16 @@
 
 package com.android.systemui.communal
 
+import android.platform.test.annotations.EnableFlags
 import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -62,6 +66,7 @@
                     powerInteractor = kosmos.powerInteractor,
                     keyguardInteractor = kosmos.keyguardInteractor,
                     keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                    communalInteractor = kosmos.communalInteractor,
                     dreamManager = dreamManager,
                     bgScope = kosmos.applicationCoroutineScope,
                 )
@@ -84,6 +89,32 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
+    fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+            runCurrent()
+
+            verify(dreamManager, never()).startDream()
+
+            kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+            kosmos.fakeKeyguardRepository.setDreaming(true)
+            runCurrent()
+
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED)
+            kosmos.fakeKeyguardRepository.setKeyguardOccluded(false)
+            kosmos.fakeKeyguardRepository.setDreaming(false)
+            runCurrent()
+
+            transition(from = KeyguardState.OCCLUDED, to = KeyguardState.DREAMING)
+            runCurrent()
+
+            verify(dreamManager).startDream()
+        }
+
+    @Test
     fun shouldNotStartDreamWhenIneligibleToDream() =
         testScope.runTest {
             keyguardRepository.setDreaming(false)
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/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 21dc953..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
@@ -69,7 +72,7 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val mockDarkAnimator = mock<ObjectAnimator>()
 
     private lateinit var underTest: StatusBarStateControllerImpl
@@ -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/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/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 177d933..35c2024 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -30,6 +30,8 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.SurfaceControl;
@@ -41,6 +43,8 @@
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.window.InputTransferToken;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.CoreStartable;
@@ -69,6 +73,9 @@
 public class Magnification implements CoreStartable, CommandQueue.Callbacks {
     private static final String TAG = "Magnification";
 
+    @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
+    private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
+
     private final ModeSwitchesController mModeSwitchesController;
     private final Context mContext;
     private final Handler mHandler;
@@ -209,8 +216,26 @@
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger) {
+        this(context, mainHandler.getLooper(), executor, commandQueue,
+                modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
+                displayTracker, displayManager, a11yLogger);
+    }
+
+    @VisibleForTesting
+    public Magnification(Context context, Looper looper, @Main Executor executor,
+            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+            SysUiState sysUiState, OverviewProxyService overviewProxyService,
+            SecureSettings secureSettings, DisplayTracker displayTracker,
+            DisplayManager displayManager, AccessibilityLogger a11yLogger) {
         mContext = context;
-        mHandler = mainHandler;
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
+                    showMagnificationButtonInternal(msg.arg1, msg.arg2);
+                }
+            }
+        };
         mExecutor = executor;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mCommandQueue = commandQueue;
@@ -350,6 +375,21 @@
 
     @MainThread
     void showMagnificationButton(int displayId, int magnificationMode) {
+        if (Flags.delayShowMagnificationButton()) {
+            if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
+                return;
+            }
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(
+                            MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
+                    DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
+        } else {
+            showMagnificationButtonInternal(displayId, magnificationMode);
+        }
+    }
+
+    @MainThread
+    private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
         // not to show mode switch button if settings panel is already showing to
         // prevent settings panel be covered by the button.
         if (isMagnificationSettingsPanelShowing(displayId)) {
@@ -360,6 +400,9 @@
 
     @MainThread
     void removeMagnificationButton(int displayId) {
+        if (Flags.delayShowMagnificationButton()) {
+            mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
+        }
         mModeSwitchesController.removeButton(displayId);
     }
 
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/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/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/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 9e7fb4e..153b7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -20,6 +20,8 @@
 import android.app.DreamManager
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.restartDreamOnUnocclude
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -27,8 +29,10 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -43,6 +47,7 @@
     private val powerInteractor: PowerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val communalInteractor: CommunalInteractor,
     private val dreamManager: DreamManager,
     @Background private val bgScope: CoroutineScope,
 ) : CoreStartable {
@@ -52,6 +57,19 @@
             return
         }
 
+        // Return to dream from occluded when not already dreaming.
+        if (restartDreamOnUnocclude()) {
+            keyguardTransitionInteractor.startedKeyguardTransitionStep
+                .sample(keyguardInteractor.isDreaming, ::Pair)
+                .filter {
+                    it.first.from == KeyguardState.OCCLUDED &&
+                        it.first.to == KeyguardState.DREAMING &&
+                        !it.second
+                }
+                .onEach { dreamManager.startDream() }
+                .launchIn(bgScope)
+        }
+
         // Restart the dream underneath the hub in order to support the ability to swipe
         // away the hub to enter the dream.
         keyguardTransitionInteractor.finishedKeyguardState
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 6b4cf79..06c8396 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -163,6 +163,13 @@
                 initialValue = false,
             )
 
+    /** Whether to start dreaming when returning from occluded */
+    val dreamFromOccluded: Flow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionStepsToState(KeyguardState.OCCLUDED)
+            .map { it.from == KeyguardState.DREAMING }
+            .stateIn(scope = applicationScope, SharingStarted.Eagerly, false)
+
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
      *
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/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/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 9b07675f..5a28f711 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
@@ -141,8 +140,6 @@
     }
 
     private fun listenForAlternateBouncerToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // Handled via #dismissAlternateBouncer.
             return
@@ -165,8 +162,6 @@
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index a306954..4d73774 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -186,7 +185,6 @@
      * PRIMARY_BOUNCER.
      */
     private fun listenForAodToPrimaryBouncer() {
-        if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForAodToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 63294f7..e738ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -94,8 +93,6 @@
     }
 
     private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -104,8 +101,6 @@
     }
 
     private fun listenForDreamingLockscreenHostedToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7961b45..c952e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -89,8 +88,6 @@
 
     private fun listenForDreamingToGlanceableHub() {
         if (!communalHub()) return
-        if (SceneContainerFlag.isEnabled)
-            return // TODO(b/336576536): Check if adaptation for scene framework is needed
         scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
             glanceableHubTransitions.listenForGlanceableHubTransition(
                 transitionOwnerName = TAG,
@@ -178,8 +175,6 @@
     }
 
     private fun listenForDreamingToGoneWhenDismissable() {
-        if (SceneContainerFlag.isEnabled)
-            return // TODO(b/336576536): Check if adaptation for scene framework is needed
         scope.launch {
             keyguardInteractor.isAbleToDream
                 .sampleCombine(
@@ -195,8 +190,6 @@
     }
 
     private fun listenForDreamingToGoneFromBiometricUnlock() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index da4e989d..faab033 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import javax.inject.Inject
@@ -63,8 +62,6 @@
     ) {
 
     override fun start() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         if (!Flags.communalHub()) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 2b3732f..c2c095b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -63,8 +62,6 @@
     ) {
 
     override fun start() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
         listenForGoneToLockscreenOrHub()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index dad2d96..56261e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,7 +20,6 @@
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
-import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -33,7 +32,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import java.util.UUID
@@ -152,7 +150,6 @@
     }
 
     private fun listenForLockscreenToPrimaryBouncer() {
-        if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -177,7 +174,6 @@
 
     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
     private fun listenForLockscreenToPrimaryBouncerDragging() {
-        if (SceneContainerFlag.isEnabled) return
         var transitionId: UUID? = null
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
             shadeRepository.legacyShadeExpansion
@@ -284,7 +280,6 @@
     }
 
     private fun listenForLockscreenToGoneDragging() {
-        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // When the refactor is enabled, we no longer use isKeyguardGoingAway.
             scope.launch("$TAG#listenForLockscreenToGoneDragging") {
@@ -342,9 +337,7 @@
      * keyguard transition.
      */
     private fun listenForLockscreenToGlanceableHub() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
-        if (!Flags.communalHub()) {
+        if (!com.android.systemui.Flags.communalHub()) {
             return
         }
         scope.launch(mainDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 9559250..2a7178f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.restartDreamOnUnocclude
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -26,7 +27,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -90,10 +90,21 @@
                     .filterRelevantKeyguardStateAnd { onTop -> !onTop }
                     .sample(
                         communalInteractor.isIdleOnCommunal,
-                        communalInteractor.showCommunalFromOccluded
+                        communalInteractor.showCommunalFromOccluded,
+                        communalInteractor.dreamFromOccluded
                     )
-                    .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) ->
-                        startTransitionToLockscreenOrHub(isIdleOnCommunal, showCommunalFromOccluded)
+                    .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) ->
+                        // Occlusion signals come from the framework, and should interrupt any
+                        // existing transition
+                        val to =
+                            if (restartDreamOnUnocclude() && dreamFromOccluded) {
+                                KeyguardState.DREAMING
+                            } else if (isIdleOnCommunal || showCommunalFromOccluded) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        startTransitionTo(to)
                     }
             }
         } else {
@@ -103,33 +114,30 @@
                         keyguardInteractor.isKeyguardShowing,
                         communalInteractor.isIdleOnCommunal,
                         communalInteractor.showCommunalFromOccluded,
+                        communalInteractor.dreamFromOccluded,
                     )
-                    .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) ->
+                    .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) ->
                         !isOccluded && isShowing
                     }
-                    .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) ->
-                        startTransitionToLockscreenOrHub(isIdleOnCommunal, showCommunalFromOccluded)
+                    .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded)
+                        ->
+                        // Occlusion signals come from the framework, and should interrupt any
+                        // existing transition
+                        val to =
+                            if (restartDreamOnUnocclude() && dreamFromOccluded) {
+                                KeyguardState.DREAMING
+                            } else if (isIdleOnCommunal || showCommunalFromOccluded) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        startTransitionTo(to)
                     }
             }
         }
     }
 
-    private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub(
-        isIdleOnCommunal: Boolean,
-        showCommunalFromOccluded: Boolean,
-    ) {
-        if (isIdleOnCommunal || showCommunalFromOccluded) {
-            // TODO(b/336576536): Check if adaptation for scene framework is needed
-            if (SceneContainerFlag.isEnabled) return
-            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
-        } else {
-            startTransitionTo(KeyguardState.LOCKSCREEN)
-        }
-    }
-
     private fun listenForOccludedToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // We don't think OCCLUDED to GONE is possible. You should always have to go via a
             // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
@@ -150,6 +158,10 @@
         }
     }
 
+    fun dismissToGone() {
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
+    }
+
     private fun listenForOccludedToAsleep() {
         scope.launch { listenForSleepTransition() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 53a0c32..181a551 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
@@ -99,8 +98,6 @@
     }
 
     private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             scope.launch {
                 keyguardInteractor.primaryBouncerShowing
@@ -161,14 +158,10 @@
     }
 
     private fun listenForPrimaryBouncerToAsleep() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch { listenForSleepTransition() }
     }
 
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .sample(keyguardInteractor.isActiveDreamLockscreenHosted, ::Pair)
@@ -181,8 +174,6 @@
     }
 
     private fun listenForPrimaryBouncerToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // This is handled in KeyguardSecurityContainerController and
             // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index fcf67d5..197221a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
@@ -50,8 +49,6 @@
         fromState: KeyguardState,
         toState: KeyguardState,
     ) {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         val toScene =
             if (fromState == KeyguardState.GLANCEABLE_HUB) {
                 CommunalScenes.Blank
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 2c05d49..a18579d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -28,11 +28,11 @@
 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.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -356,8 +356,6 @@
      * state.
      */
     fun startDismissKeyguardTransition() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
         when (val startedState = startedKeyguardState.replayCache.last()) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index dc35e43..bb2eeb7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -16,16 +16,11 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
-import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +42,6 @@
     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
     fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
     notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
-    sceneInteractor: SceneInteractor,
 ) {
     private val defaultSurfaceBehindVisibility =
         transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -109,42 +103,21 @@
      * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
      */
     val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
-        if (SceneContainerFlag.isEnabled) {
-            combine(
-                    sceneInteractor.transitionState,
-                    surfaceBehindInteractor.isAnimatingSurface,
-                    notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
-                ) { transition, isAnimatingSurface, isLaunchAnimationRunning ->
-                    // Using the animation if we're animating it directly, or if the
-                    // ActivityLaunchAnimator is in the process of animating it.
-                    val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning
-                    // We may still be animating the surface after the keyguard is fully GONE, since
-                    // some animations (like the translation spring) are not tied directly to the
-                    // transition step amount.
-                    transition.isTransitioning(to = Scenes.Gone) ||
-                        (isAnyAnimationRunning &&
-                            (transition.isIdle(Scenes.Gone) ||
-                                transition.isTransitioning(from = Scenes.Gone)))
-                }
-                .distinctUntilChanged()
-        } else {
-            combine(
-                    transitionInteractor.isInTransitionToState(KeyguardState.GONE),
-                    transitionInteractor.finishedKeyguardState,
-                    surfaceBehindInteractor.isAnimatingSurface,
-                    notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
-                ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
-                    // Using the animation if we're animating it directly, or if the
-                    // ActivityLaunchAnimator is in the process of animating it.
-                    val animationsRunning = isAnimatingSurface || notifLaunchRunning
-                    // We may still be animating the surface after the keyguard is fully GONE, since
-                    // some animations (like the translation spring) are not tied directly to the
-                    // transition step amount.
-                    isInTransitionToGone ||
-                        (finishedState == KeyguardState.GONE && animationsRunning)
-                }
-                .distinctUntilChanged()
-        }
+        combine(
+                transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+                transitionInteractor.finishedKeyguardState,
+                surfaceBehindInteractor.isAnimatingSurface,
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+                // Using the animation if we're animating it directly, or if the
+                // ActivityLaunchAnimator is in the process of animating it.
+                val animationsRunning = isAnimatingSurface || notifLaunchRunning
+                // We may still be animating the surface after the keyguard is fully GONE, since
+                // some animations (like the translation spring) are not tied directly to the
+                // transition step amount.
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
+            }
+            .distinctUntilChanged()
 
     /**
      * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
@@ -154,44 +127,28 @@
      * want to know if the AOD/clock/notifs/etc. are visible.
      */
     val lockscreenVisibility: Flow<Boolean> =
-        if (SceneContainerFlag.isEnabled) {
-            sceneInteractor.transitionState
-                .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen))
-                .map { (prevTransitionState, transitionState) ->
-                    val isReturningToGoneAfterCancellation =
-                        prevTransitionState.isTransitioning(from = Scenes.Gone) &&
-                            transitionState.isTransitioning(to = Scenes.Gone)
-                    val isNotOnGone =
-                        !transitionState.isTransitioning(from = Scenes.Gone) &&
-                            !transitionState.isIdle(Scenes.Gone)
+        transitionInteractor.currentKeyguardState
+            .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
+            .map { (currentState, startedWithPrev) ->
+                val startedFromStep = startedWithPrev?.previousValue
+                val startedStep = startedWithPrev?.newValue
+                val returningToGoneAfterCancellation =
+                    startedStep?.to == KeyguardState.GONE &&
+                        startedFromStep?.transitionState == TransitionState.CANCELED &&
+                        startedFromStep.from == KeyguardState.GONE
 
-                    isNotOnGone && !isReturningToGoneAfterCancellation
+                if (!returningToGoneAfterCancellation) {
+                    // By default, apply the lockscreen visibility of the current state.
+                    KeyguardState.lockscreenVisibleInState(currentState)
+                } else {
+                    // If we're transitioning to GONE after a prior canceled transition from GONE,
+                    // then this is the camera launch transition from an asleep state back to GONE.
+                    // We don't want to show the lockscreen since we're aborting the lock and going
+                    // back to GONE.
+                    KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
                 }
-                .distinctUntilChanged()
-        } else {
-            transitionInteractor.currentKeyguardState
-                .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
-                .map { (currentState, startedWithPrev) ->
-                    val startedFromStep = startedWithPrev?.previousValue
-                    val startedStep = startedWithPrev?.newValue
-                    val returningToGoneAfterCancellation =
-                        startedStep?.to == KeyguardState.GONE &&
-                            startedFromStep?.transitionState == TransitionState.CANCELED &&
-                            startedFromStep.from == KeyguardState.GONE
-
-                    if (!returningToGoneAfterCancellation) {
-                        // By default, apply the lockscreen visibility of the current state.
-                        KeyguardState.lockscreenVisibleInState(currentState)
-                    } else {
-                        // If we're transitioning to GONE after a prior canceled transition from
-                        // GONE, then this is the camera launch transition from an asleep state back
-                        // to GONE. We don't want to show the lockscreen since we're aborting the
-                        // lock and going back to GONE.
-                        KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
-                    }
-                }
-                .distinctUntilChanged()
-        }
+            }
+            .distinctUntilChanged()
 
     /**
      * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
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/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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7983db1..2446473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -199,12 +199,14 @@
     protected boolean mPowerPluggedInWired;
     protected boolean mPowerPluggedInWireless;
     protected boolean mPowerPluggedInDock;
+    protected int mChargingSpeed;
 
     private boolean mPowerCharged;
+    /** Whether the battery defender is triggered. */
     private boolean mBatteryDefender;
+    /** Whether the battery defender is triggered with the device plugged. */
     private boolean mEnableBatteryDefender;
     private boolean mIncompatibleCharger;
-    protected int mChargingSpeed;
     private int mChargingWattage;
     private int mBatteryLevel;
     private boolean mBatteryPresent = true;
@@ -1244,7 +1246,7 @@
             mChargingSpeed = status.getChargingSpeed(mContext);
             mBatteryLevel = status.level;
             mBatteryPresent = status.present;
-            mBatteryDefender = status.isBatteryDefender();
+            mBatteryDefender = isBatteryDefender(status);
             // when the battery is overheated, device doesn't charge so only guard on pluggedIn:
             mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn();
             mIncompatibleCharger = status.incompatibleCharger.orElse(false);
@@ -1516,6 +1518,11 @@
         return mPowerPluggedIn;
     }
 
+    /** Return true if the device is under the battery defender mode. */
+    protected boolean isBatteryDefender(BatteryStatus status) {
+        return status.isBatteryDefender();
+    }
+
     private boolean isCurrentUser(int userId) {
         return getCurrentUser() == userId;
     }
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/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/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/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 25e5470..3164f8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility;
 
+import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -23,11 +25,17 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -39,6 +47,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
@@ -47,6 +56,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -58,9 +68,12 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class IMagnificationConnectionTest extends SysuiTestCase {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     @Mock
     private AccessibilityManager mAccessibilityManager;
@@ -90,6 +103,7 @@
     private IMagnificationConnection mIMagnificationConnection;
     private Magnification mMagnification;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+    private TestableLooper mTestableLooper;
 
     @Before
     public void setUp() throws Exception {
@@ -100,8 +114,10 @@
             return null;
         }).when(mAccessibilityManager).setMagnificationConnection(
                 any(IMagnificationConnection.class));
+        mTestableLooper = TestableLooper.get(this);
+        assertNotNull(mTestableLooper);
         mMagnification = new Magnification(getContext(),
-                getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
+                mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
         mMagnification.mWindowMagnificationControllerSupplier =
@@ -122,7 +138,7 @@
     public void enableWindowMagnification_passThrough() throws RemoteException {
         mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN,
                 Float.NaN, 0f, 0f, mAnimationCallback);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).enableWindowMagnification(eq(3.0f),
                 eq(Float.NaN), eq(Float.NaN), eq(0f), eq(0f), eq(mAnimationCallback));
@@ -131,7 +147,7 @@
     @Test
     public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
         mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mFullscreenMagnificationController)
                 .onFullscreenMagnificationActivationChanged(eq(true));
@@ -141,7 +157,7 @@
     public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
         mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
                 mAnimationCallback);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).deleteWindowMagnification(
                 mAnimationCallback);
@@ -150,7 +166,7 @@
     @Test
     public void setScaleForWindowMagnification() throws RemoteException {
         mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).setScale(3.0f);
     }
@@ -158,7 +174,7 @@
     @Test
     public void moveWindowMagnifier() throws RemoteException {
         mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
     }
@@ -167,37 +183,102 @@
     public void moveWindowMagnifierToPosition() throws RemoteException {
         mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
                 100f, 200f, mAnimationCallback);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).moveWindowMagnifierToPosition(
                 eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class));
     }
 
     @Test
-    public void showMagnificationButton() throws RemoteException {
+    @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+    public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException {
         // magnification settings panel should not be showing
         assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
 
         mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mModeSwitchesController).showButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+    public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException {
+        // magnification settings panel should not be showing
+        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+        // showMagnificationButton request to Magnification.
+        processAllPendingMessages();
+
+        // The delayed message would be processed after DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS.
+        // So call this processAllPendingMessages with a timeout to verify the showButton
+        // will be called.
+        int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100;
+        processAllPendingMessages(timeout);
+        verify(mModeSwitchesController).showButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void showMagnificationButton_settingsPanelShowing_doNotShowButton()
+            throws RemoteException {
+        when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(true);
+
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+        // showMagnificationButton request to Magnification.
+        processAllPendingMessages();
+
+        // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so
+        // process all message after a timeout here to verify the showButton will not be called.
+        int timeout = Flags.delayShowMagnificationButton()
+                ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100
+                : 0;
+        processAllPendingMessages(timeout);
+        verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
     public void removeMagnificationButton() throws RemoteException {
         mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mModeSwitchesController).removeButton(TEST_DISPLAY);
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+    public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout()
+            throws RemoteException {
+        // magnification settings panel should not be showing
+        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
+        // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+        // requests to Magnification.
+        processAllPendingMessages();
+
+        // Call this processAllPendingMessages with a timeout to ensure the delayed show button
+        // message should be removed and thus the showButton will not be called after timeout.
+        int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100;
+        processAllPendingMessages(/* timeForwardMs= */ timeout);
+        verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
     public void removeMagnificationSettingsPanel() throws RemoteException {
         mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mMagnificationSettingsController).closeMagnificationSettings();
     }
@@ -208,7 +289,7 @@
         final float testScale = 3.0f;
         mIMagnificationConnection.onUserMagnificationScaleChanged(
                 testUserId, TEST_DISPLAY, testScale);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         assertTrue(mMagnification.mUsersScales.contains(testUserId));
         assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
@@ -216,6 +297,17 @@
         verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
     }
 
+    private void processAllPendingMessages() {
+        processAllPendingMessages(/* timeForwardMs=*/ 0);
+    }
+
+    private void processAllPendingMessages(int timeForwardMs) {
+        if (timeForwardMs > 0) {
+            mTestableLooper.moveTimeForward(timeForwardMs);
+        }
+        mTestableLooper.processAllMessages();
+    }
+
     private class FakeWindowMagnificationControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
@@ -229,7 +321,6 @@
         }
     }
 
-
     private class FakeFullscreenMagnificationControllerSupplier extends
             DisplayIdIndexSupplier<FullscreenMagnificationController> {
 
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/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 687e91a..c3a806b 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.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
@@ -1424,6 +1425,39 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
+    @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
+    fun dreamingToOccludedToDreaming() =
+        testScope.runTest {
+            // GIVEN a device on lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // Given a device that is dreaming
+            keyguardRepository.setDreaming(true)
+
+            // GIVEN a prior transition has run to OCCLUDED
+            runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.OCCLUDED)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // WHEN occlusion ends
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            // THEN a transition to GLANCEABLE_HUB should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromOccludedTransitionInteractor::class.simpleName,
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.DREAMING,
+                    animatorAssertion = { it.isNotNull() },
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun dreamingToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index a77169e..b1a8dd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -18,29 +18,20 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.flags.DisableSceneContainer
-import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 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.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -66,22 +57,14 @@
                 .thenReturn(surfaceBehindIsAnimatingFlow)
         }
 
-    private val underTest = lazy { kosmos.windowManagerLockscreenVisibilityInteractor }
+    private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor
     private val testScope = kosmos.testScope
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
-    @Before
-    fun setUp() {
-        // lazy value needs to be called here otherwise flow collection misbehaves
-        underTest.value
-        kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
-    }
-
     @Test
-    @DisableSceneContainer
     fun surfaceBehindVisibility_switchesToCorrectFlow() =
         testScope.runTest {
-            val values by collectValues(underTest.value.surfaceBehindVisibility)
+            val values by collectValues(underTest.surfaceBehindVisibility)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -187,10 +170,9 @@
         }
 
     @Test
-    @DisableSceneContainer
     fun testUsingGoingAwayAnimation_duringTransitionToGone() =
         testScope.runTest {
-            val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation)
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -248,10 +230,9 @@
         }
 
     @Test
-    @DisableSceneContainer
     fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
         testScope.runTest {
-            val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation)
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -338,10 +319,9 @@
         }
 
     @Test
-    @DisableSceneContainer
     fun lockscreenVisibility_visibleWhenGone() =
         testScope.runTest {
-            val values by collectValues(underTest.value.lockscreenVisibility)
+            val values by collectValues(underTest.lockscreenVisibility)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -405,10 +385,9 @@
         }
 
     @Test
-    @DisableSceneContainer
     fun testLockscreenVisibility_usesFromState_ifCanceled() =
         testScope.runTest {
-            val values by collectValues(underTest.value.lockscreenVisibility)
+            val values by collectValues(underTest.lockscreenVisibility)
 
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -507,10 +486,9 @@
      * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true).
      */
     @Test
-    @DisableSceneContainer
     fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() =
         testScope.runTest {
-            val values by collectValues(underTest.value.lockscreenVisibility)
+            val values by collectValues(underTest.lockscreenVisibility)
 
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -609,11 +587,11 @@
             )
         }
 
+    /**  */
     @Test
-    @DisableSceneContainer
     fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() =
         testScope.runTest {
-            val values by collectValues(underTest.value.lockscreenVisibility)
+            val values by collectValues(underTest.lockscreenVisibility)
 
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -724,109 +702,4 @@
                 values
             )
         }
-
-    @Test
-    @EnableSceneContainer
-    fun sceneContainer_lockscreenVisibility_visibleWhenNotGone() =
-        testScope.runTest {
-            val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
-
-            sceneTransitions.value = lsToGone
-            assertThat(lockscreenVisibility).isTrue()
-
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
-            assertThat(lockscreenVisibility).isFalse()
-
-            sceneTransitions.value = goneToLs
-            assertThat(lockscreenVisibility).isFalse()
-
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
-            assertThat(lockscreenVisibility).isTrue()
-        }
-
-    @Test
-    @EnableSceneContainer
-    fun sceneContainer_lockscreenVisibility_notVisibleWhenReturningToGone() =
-        testScope.runTest {
-            val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
-
-            sceneTransitions.value = goneToLs
-            assertThat(lockscreenVisibility).isFalse()
-
-            sceneTransitions.value = lsToGone
-            assertThat(lockscreenVisibility).isFalse()
-
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
-            assertThat(lockscreenVisibility).isFalse()
-
-            sceneTransitions.value = goneToLs
-            assertThat(lockscreenVisibility).isFalse()
-
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
-            assertThat(lockscreenVisibility).isTrue()
-        }
-
-    @Test
-    @EnableSceneContainer
-    fun sceneContainer_usingGoingAwayAnimation_duringTransitionToGone() =
-        testScope.runTest {
-            val usingKeyguardGoingAwayAnimation by
-                collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation)
-
-            sceneTransitions.value = lsToGone
-            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
-
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
-            assertThat(usingKeyguardGoingAwayAnimation).isFalse()
-        }
-
-    @Test
-    @EnableSceneContainer
-    fun sceneContainer_usingGoingAwayAnimation_surfaceBehindIsAnimating() =
-        testScope.runTest {
-            val usingKeyguardGoingAwayAnimation by
-                collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation)
-
-            sceneTransitions.value = lsToGone
-            surfaceBehindIsAnimatingFlow.emit(true)
-            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
-
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
-            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
-
-            sceneTransitions.value = goneToLs
-            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
-
-            surfaceBehindIsAnimatingFlow.emit(false)
-            assertThat(usingKeyguardGoingAwayAnimation).isFalse()
-        }
-
-    companion object {
-        private val progress = MutableStateFlow(0f)
-
-        private val sceneTransitions =
-            MutableStateFlow<ObservableTransitionState>(
-                ObservableTransitionState.Idle(Scenes.Lockscreen)
-            )
-
-        private val lsToGone =
-            ObservableTransitionState.Transition(
-                Scenes.Lockscreen,
-                Scenes.Gone,
-                flowOf(Scenes.Lockscreen),
-                progress,
-                false,
-                flowOf(false)
-            )
-
-        private val goneToLs =
-            ObservableTransitionState.Transition(
-                Scenes.Gone,
-                Scenes.Lockscreen,
-                flowOf(Scenes.Lockscreen),
-                progress,
-                false,
-                flowOf(false)
-            )
-    }
 }
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/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/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aac3640..56e5e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -138,6 +138,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index b38acc8..29167d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
 
 val Kosmos.windowManagerLockscreenVisibilityInteractor by
@@ -30,6 +29,5 @@
             fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
             fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor,
             notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
-            sceneInteractor = sceneInteractor,
         )
     }
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/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 763879e..6fc05b7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -1998,6 +1998,19 @@
         }
 
         @Override
+        public void setViewAutofilled(int sessionId, @NonNull AutofillId id, int userId) {
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service =
+                        peekServiceForUserWithLocalBinderIdentityLocked(userId);
+                if (service != null) {
+                    service.setViewAutofilled(sessionId, getCallingUid(), id);
+                } else if (sVerbose) {
+                    Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
+                }
+            }
+        }
+
+        @Override
         public void finishSession(int sessionId, int userId,
                 @AutofillCommitReason int commitReason) {
             synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 92acce2..588266f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -466,6 +466,7 @@
     @GuardedBy("mLock")
     void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
         if (!isEnabledLocked()) {
+            Slog.wtf(TAG, "Service not enabled");
             return;
         }
         final Session session = mSessions.get(sessionId);
@@ -477,8 +478,23 @@
     }
 
     @GuardedBy("mLock")
+    void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) {
+        if (!isEnabledLocked()) {
+            Slog.wtf(TAG, "Service not enabled");
+            return;
+        }
+        final Session session = mSessions.get(sessionId);
+        if (session == null || uid != session.uid) {
+            Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")");
+            return;
+        }
+        session.setViewAutofilled(id);
+    }
+
+    @GuardedBy("mLock")
     void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) {
         if (!isEnabledLocked()) {
+            Slog.wtf(TAG, "Service not enabled");
             return;
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 9c84b12..f289115 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -65,6 +65,7 @@
 import android.provider.Settings;
 import android.service.autofill.Dataset;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
@@ -548,9 +549,10 @@
     /**
      * Set views_fillable_total_count as long as mEventInternal presents.
      */
-    public void maybeSetViewFillableCounts(int totalFillableCount) {
+    public void maybeSetViewFillablesAndCount(List<AutofillId> autofillIds) {
         mEventInternal.ifPresent(event -> {
-            event.mViewFillableTotalCount = totalFillableCount;
+            event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds);
+            event.mViewFillableTotalCount = event.mAutofillIdsAttemptedAutofill.size();
         });
     }
 
@@ -564,6 +566,41 @@
         });
     }
 
+    /** 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;
+        });
+    }
+    /**
+     * Set views_filled_failure_count using failure count as long as mEventInternal
+     * presents.
+     */
+    public void maybeAddSuccessId(AutofillId autofillId) {
+        mEventInternal.ifPresent(event -> {
+            ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill;
+            if (autofillIds == null) {
+                Slog.w(TAG, "Attempted autofill ids is null, but received autofillId:" + autofillId
+                        + " successfully filled");
+                event.mViewFilledButUnexpectedCount++;
+            } else if (autofillIds.contains(autofillId)) {
+                if (sVerbose) {
+                    Slog.v(TAG, "Logging autofill for id:" + autofillId);
+                    event.mViewFillSuccessCount++;
+                }
+            } else {
+                Slog.w(TAG, "Successfully filled autofillId:" + autofillId
+                        + " not found in list of attempted autofill ids: " + autofillIds);
+                event.mViewFilledButUnexpectedCount++;
+            }
+        });
+    }
+
     public void logAndEndEvent() {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -608,7 +645,10 @@
                     + " mIsCredentialRequest=" + event.mIsCredentialRequest
                     + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
                     + " mViewFillableTotalCount=" + event.mViewFillableTotalCount
-                    + " mViewFillFailureCount=" + event.mViewFillFailureCount);
+                    + " mViewFillFailureCount=" + event.mViewFillFailureCount
+                    + " mFocusedId=" + event.mFocusedId
+                    + " mViewFillSuccessCount=" + event.mViewFillSuccessCount
+                    + " mViewFilledButUnexpectedCount=" + event.mViewFilledButUnexpectedCount);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -651,7 +691,10 @@
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
                 event.mViewFillableTotalCount,
-                event.mViewFillFailureCount);
+                event.mViewFillFailureCount,
+                event.mFocusedId,
+                event.mViewFillSuccessCount,
+                event.mViewFilledButUnexpectedCount);
         mEventInternal = Optional.empty();
     }
 
@@ -689,7 +732,14 @@
         boolean mWebviewRequestedCredential = false;
         int mViewFillableTotalCount = -1;
         int mViewFillFailureCount = -1;
+        int mFocusedId = -1;
 
+        // Default value for success count is set to 0 explicitly. Setting it to -1 for
+        // uninitialized doesn't help much, as this would be non-zero only if callback is received.
+        int mViewFillSuccessCount = 0;
+        int mViewFilledButUnexpectedCount = 0;
+
+        ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
         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..07b16c5 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")
@@ -5381,7 +5382,20 @@
             }
         }
         mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
-        mPresentationStatsEventLogger.logAndEndEvent();
+    }
+
+    /**
+     * Sets the state of views that failed to autofill.
+     */
+    @GuardedBy("mLock")
+    void setViewAutofilled(@NonNull AutofillId id) {
+        if (sVerbose) {
+            Slog.v(TAG, "View autofilled: " + id);
+        }
+        if (id.getSessionId() == AutofillId.NO_SESSION) {
+            id.setSessionId(this.id);
+        }
+        mPresentationStatsEventLogger.maybeAddSuccessId(id);
     }
 
     @GuardedBy("mLock")
@@ -6589,7 +6603,7 @@
                     if (sVerbose) {
                         Slog.v(TAG, "Total views to be autofilled: " + ids.size());
                     }
-                    mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size());
+                    mPresentationStatsEventLogger.maybeSetViewFillablesAndCount(ids);
                     if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
                     mClient.autofill(id, ids, values, hideHighlight);
                     if (dataset.getId() != null) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 70443f9..38a412f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -118,7 +118,7 @@
         final InputMethodManagerInternal inputMethodManagerInternal =
                 LocalServices.getService(InputMethodManagerInternal.class);
         if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
-                displayId)) {
+                displayId, mUserId)) {
             Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
             mOnErrorCallback.run();
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d7c65c7..70af49c 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -224,7 +224,7 @@
         token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
         mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
         String phys = inputDeviceDescriptor.getPhys();
-        InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+        InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
         // Type associations are added in the case of navigation touchpads. Those should be removed
         // once the input device gets closed.
         if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
@@ -287,7 +287,7 @@
 
     private void setUniqueIdAssociation(int displayId, String phys) {
         final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
-        InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+        InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId);
     }
 
     boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
@@ -789,7 +789,7 @@
                 throw e;
             }
         } catch (DeviceCreationException e) {
-            InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+            InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
             throw e;
         }
 
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 bef5c612..94bf813 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -157,6 +157,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
 import android.app.Service;
 import android.app.ServiceStartArgs;
 import android.app.StartForegroundCalledOnStoppedServiceException;
@@ -784,7 +785,7 @@
                 ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                 "SERVICE_FOREGROUND_TIMEOUT");
         this.mFGSAnrTimer = new ServiceAnrTimer(service,
-                ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
+                ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG,
                 "FGS_TIMEOUT");
     }
 
@@ -2456,12 +2457,14 @@
                                             + " foreground service type "
                                             + ServiceInfo.foregroundServiceTypeToLabel(
                                                     foregroundServiceType);
-                                    if (!android.app.Flags.gateFgsTimeoutAnrBehavior()) {
+                                    // Only throw an exception if the new ANR behavior
+                                    // ("do nothing") is not gated or the new crashing logic gate
+                                    // is enabled; otherwise, reset the limit temporarily.
+                                    if (!android.app.Flags.gateFgsTimeoutAnrBehavior()
+                                            || android.app.Flags.enableFgsTimeoutCrashBehavior()) {
                                         throw new ForegroundServiceStartNotAllowedException(
                                                     exceptionMsg);
                                     } else {
-                                        // Only throw an exception above while the new ANR behavior
-                                        // is not gated, otherwise, reset the limit temporarily.
                                         Slog.wtf(TAG, exceptionMsg);
                                         fgsTypeInfo.reset();
                                     }
@@ -3936,12 +3939,12 @@
                 Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
             }
 
-            // ANR the service after giving the service some time to clean up.
-            mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration);
+            // Crash the service after giving the service some time to clean up.
+            mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration);
         }
     }
 
-    void onFgsAnrTimeout(ServiceRecord sr) {
+    void onFgsCrashTimeout(ServiceRecord sr) {
         final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
         if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
             return; // no timed out FGS type was found (either it was stopped or it switched types)
@@ -3956,20 +3959,36 @@
             return;
         }
 
-        final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
-        tr.mLatencyTracker.waitingOnAMSLockStarted();
-        synchronized (mAm) {
-            tr.mLatencyTracker.waitingOnAMSLockEnded();
-
-            Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
-            traceInstant("FGS ANR: ", sr);
-            if (sr.app != null) {
-                mAm.appNotResponding(sr.app, tr);
+        if (android.app.Flags.enableFgsTimeoutCrashBehavior()) {
+            // Crash the app
+            synchronized (mAm) {
+                Slog.e(TAG_SERVICE, "FGS Crashed: " + sr);
+                traceInstant("FGS Crash: ", sr);
+                if (sr.app != null) {
+                    mAm.crashApplicationWithTypeWithExtras(sr.app.uid, sr.app.getPid(),
+                            sr.app.info.packageName, sr.app.userId, reason, false /*force*/,
+                            ForegroundServiceDidNotStopInTimeException.TYPE_ID,
+                            ForegroundServiceDidNotStopInTimeException
+                                    .createExtrasForService(sr.getComponentName()));
+                }
             }
+        } else {
+            // ANR the app if the new crash behavior is not enabled
+            final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
+            tr.mLatencyTracker.waitingOnAMSLockStarted();
+            synchronized (mAm) {
+                tr.mLatencyTracker.waitingOnAMSLockEnded();
 
-            // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
-            // dialog really doesn't remember the "cause" (especially if there have been multiple
-            // ANRs), so it's not doable.
+                Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
+                traceInstant("FGS ANR: ", sr);
+                if (sr.app != null) {
+                    mAm.appNotResponding(sr.app, tr);
+                }
+
+                // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
+                // dialog really doesn't remember the "cause" (especially if there have been
+                // multiple ANRs), so it's not doable.
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9e06b75..26aa053 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1115,17 +1115,17 @@
 
     /**
      * If a service of a timeout-enforced type doesn't finish within this duration after its
-     * timeout, then we'll declare an ANR.
+     * timeout, then we'll crash the app.
      * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then
-     * the app will be ANR'ed 1 hour and 10 seconds after it started.
+     * the app will crash 1 hour and 10 seconds after it started.
      */
-    private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration";
+    private static final String KEY_FGS_CRASH_EXTRA_WAIT_DURATION = "fgs_crash_extra_wait_duration";
 
-    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
-    static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+    /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */
+    static final long DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION = 10_000;
 
-    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
-    public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION;
+    /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */
+    public volatile long mFgsCrashExtraWaitDuration = DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION;
 
     /** @see #KEY_USE_TIERED_CACHED_ADJ */
     public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
@@ -1315,8 +1315,8 @@
                             case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
                                 updateShortFgsAnrExtraWaitDuration();
                                 break;
-                            case KEY_FGS_ANR_EXTRA_WAIT_DURATION:
-                                updateFgsAnrExtraWaitDuration();
+                            case KEY_FGS_CRASH_EXTRA_WAIT_DURATION:
+                                updateFgsCrashExtraWaitDuration();
                                 break;
                             case KEY_PROACTIVE_KILLS_ENABLED:
                                 updateProactiveKillsEnabled();
@@ -2199,11 +2199,11 @@
                 DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION);
     }
 
-    private void updateFgsAnrExtraWaitDuration() {
-        mFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+    private void updateFgsCrashExtraWaitDuration() {
+        mFgsCrashExtraWaitDuration = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_FGS_ANR_EXTRA_WAIT_DURATION,
-                DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION);
+                KEY_FGS_CRASH_EXTRA_WAIT_DURATION,
+                DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION);
     }
 
     private void updateEnableWaitForFinishAttachApplication() {
@@ -2456,8 +2456,8 @@
         pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration);
         pw.print("  "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION);
         pw.print("="); pw.println(mDataSyncFgsTimeoutDuration);
-        pw.print("  "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION);
-        pw.print("="); pw.println(mFgsAnrExtraWaitDuration);
+        pw.print("  "); pw.print(KEY_FGS_CRASH_EXTRA_WAIT_DURATION);
+        pw.print("="); pw.println(mFgsCrashExtraWaitDuration);
 
         pw.print("  "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
         pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 00c2df6..46ed1fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1670,7 +1670,7 @@
     static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
     static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
     static final int SERVICE_FGS_TIMEOUT_MSG = 84;
-    static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85;
+    static final int SERVICE_FGS_CRASH_TIMEOUT_MSG = 85;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -2041,8 +2041,8 @@
                 case SERVICE_FGS_TIMEOUT_MSG: {
                     mServices.onFgsTimeout((ServiceRecord) msg.obj);
                 } break;
-                case SERVICE_FGS_ANR_TIMEOUT_MSG: {
-                    mServices.onFgsAnrTimeout((ServiceRecord) msg.obj);
+                case SERVICE_FGS_CRASH_TIMEOUT_MSG: {
+                    mServices.onFgsCrashTimeout((ServiceRecord) msg.obj);
                 } break;
             }
         }
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/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 645a366..390ee96 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
+import android.graphics.ImageFormat;
 import android.graphics.Rect;
 import android.hardware.CameraExtensionSessionStats;
 import android.hardware.CameraSessionStats;
@@ -906,6 +907,7 @@
 
             int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE;
             boolean extensionIsAdvanced = false;
+            int extensionCaptureFormat = ImageFormat.UNKNOWN;
             if (e.mExtSessionStats != null) {
                 switch (e.mExtSessionStats.type) {
                     case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC:
@@ -932,6 +934,9 @@
                         Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type);
                 }
                 extensionIsAdvanced = e.mExtSessionStats.isAdvanced;
+                if (Flags.analytics24q3()) {
+                    extensionCaptureFormat = e.mExtSessionStats.captureFormat;
+                }
             }
 
             int streamCount = 0;
@@ -945,10 +950,13 @@
                 String zoomOverrideDebug = Flags.logZoomOverrideUsage()
                         ? ", zoomOverrideUsage " + e.mUsedZoomOverride
                         : "";
-
                 String mostRequestedFpsRangeDebug = Flags.analytics24q3()
                         ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
                         : "";
+                String extensionCaptureFormatDebug = Flags.analytics24q3()
+                        ? " extensionCaptureFormat " + e.mExtSessionStats.captureFormat
+                        : "";
+
                 Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
                         + " clientName " + e.mClientName
                         + ", duration " + e.getDuration()
@@ -971,8 +979,10 @@
                         + ", logId " + e.mLogId
                         + ", sessionIndex " + e.mSessionIndex
                         + ", mExtSessionStats {type " + extensionType
-                        + " isAdvanced " + extensionIsAdvanced + "}");
+                        + " isAdvanced " + extensionIsAdvanced
+                        + extensionCaptureFormatDebug + "}");
             }
+
             // Convert from CameraStreamStats to CameraStreamProto
             CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
             for (int i = 0; i < MAX_STREAM_STATISTICS; i++) {
@@ -1035,7 +1045,8 @@
                     e.mLogId, e.mSessionIndex,
                     extensionType, extensionIsAdvanced, e.mUsedUltraWide,
                     e.mUsedZoomOverride,
-                    e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+                    e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper(),
+                    extensionCaptureFormat);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 22f3bbd..fa8299b 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -29,7 +29,7 @@
  */
 public class DisplayControl {
     private static native IBinder nativeCreateDisplay(String name, boolean secure,
-            float requestedRefreshRate);
+            String uniqueId, float requestedRefreshRate);
     private static native void nativeDestroyDisplay(IBinder displayToken);
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
     private static native long[] nativeGetPhysicalDisplayIds();
@@ -43,20 +43,21 @@
     /**
      * Create a display in SurfaceFlinger.
      *
-     * @param name The name of the display
+     * @param name The name of the display.
      * @param secure Whether this display is secure.
      * @return The token reference for the display in SurfaceFlinger.
      */
     public static IBinder createDisplay(String name, boolean secure) {
         Objects.requireNonNull(name, "name must not be null");
-        return nativeCreateDisplay(name, secure, 0.0f);
+        return nativeCreateDisplay(name, secure, "", 0.0f);
     }
 
     /**
      * Create a display in SurfaceFlinger.
      *
-     * @param name The name of the display
+     * @param name The name of the display.
      * @param secure Whether this display is secure.
+     * @param uniqueId The unique ID for the display.
      * @param requestedRefreshRate The requested refresh rate in frames per second.
      * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
      * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
@@ -65,9 +66,10 @@
      * @return The token reference for the display in SurfaceFlinger.
      */
     public static IBinder createDisplay(String name, boolean secure,
-            float requestedRefreshRate) {
+            String uniqueId, float requestedRefreshRate) {
         Objects.requireNonNull(name, "name must not be null");
-        return nativeCreateDisplay(name, secure, requestedRefreshRate);
+        Objects.requireNonNull(uniqueId, "uniqueId must not be null");
+        return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate);
     }
 
     /**
@@ -79,7 +81,6 @@
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-
         nativeDestroyDisplay(displayToken);
     }
 
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/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index bcdb442..a29e852 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -92,8 +92,9 @@
             Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) {
         this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
             @Override
-            public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
-                return DisplayControl.createDisplay(name, secure, requestedRefreshRate);
+            public IBinder createDisplay(String name, boolean secure, String uniqueId,
+                                         float requestedRefreshRate) {
+                return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate);
             }
 
             @Override
@@ -126,7 +127,7 @@
         String name = virtualDisplayConfig.getName();
         boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
 
-        IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
+        IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId,
                 virtualDisplayConfig.getRequestedRefreshRate());
         MediaProjectionCallback mediaProjectionCallback =  null;
         if (projection != null) {
@@ -653,8 +654,9 @@
         /**
          * Create a virtual display in SurfaceFlinger.
          *
-         * @param name The name of the display
+         * @param name The name of the display.
          * @param secure Whether this display is secure.
+         * @param uniqueId The unique ID for the display.
          * @param requestedRefreshRate
          *     The refresh rate, frames per second, to request on the virtual display.
          *     It should be a divisor of refresh rate of the leader physical display
@@ -663,8 +665,9 @@
          *     the refresh rate of the leader physical display.
          * @return The token reference for the display in SurfaceFlinger.
          */
-        IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
-        
+        IBinder createDisplay(String name, boolean secure, String uniqueId,
+                              float requestedRefreshRate);
+
         /**
          * Destroy a display in SurfaceFlinger.
          *
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 308aed6..8317991 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -254,7 +254,7 @@
     // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a
     // specific display.
     @GuardedBy("mAssociationsLock")
-    private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+    private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>();
 
     // The associations of input devices to displays by descriptor. Maps from
     // {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the
@@ -1656,7 +1656,8 @@
     }
 
     @Override // Binder call
-    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+            @NonNull String displayUniqueId) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "addUniqueIdAssociation()")) {
@@ -1667,13 +1668,13 @@
         Objects.requireNonNull(inputPort);
         Objects.requireNonNull(displayUniqueId);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.put(inputPort, displayUniqueId);
+            mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId);
         }
         mNative.changeUniqueIdAssociation();
     }
 
     @Override // Binder call
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "removeUniqueIdAssociation()")) {
@@ -1682,7 +1683,7 @@
 
         Objects.requireNonNull(inputPort);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.remove(inputPort);
+            mUniqueIdAssociationsByPort.remove(inputPort);
         }
         mNative.changeUniqueIdAssociation();
     }
@@ -2101,9 +2102,9 @@
                     pw.println("  display: " + v);
                 });
             }
-            if (!mUniqueIdAssociations.isEmpty()) {
+            if (!mUniqueIdAssociationsByPort.isEmpty()) {
                 pw.println("Unique Id Associations:");
-                mUniqueIdAssociations.forEach((k, v) -> {
+                mUniqueIdAssociationsByPort.forEach((k, v) -> {
                     pw.print("  port: " + k);
                     pw.println("  uniqueId: " + v);
                 });
@@ -2530,10 +2531,10 @@
 
     // Native callback
     @SuppressWarnings("unused")
-    private String[] getInputUniqueIdAssociations() {
+    private String[] getInputUniqueIdAssociationsByPort() {
         final Map<String, String> associations;
         synchronized (mAssociationsLock) {
-            associations = new HashMap<>(mUniqueIdAssociations);
+            associations = new HashMap<>(mUniqueIdAssociationsByPort);
         }
 
         return flatten(associations);
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index 6eae9a4..d7d57df 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -240,7 +240,8 @@
             return;
         }
 
-        post(() -> handleRotaryInput(MotionEvent.obtain((MotionEvent) event)));
+        MotionEvent motionEvent = MotionEvent.obtain(event);
+        post(() -> handleRotaryInput(motionEvent));
     }
 
     private void handleKeyEvent(KeyEvent keyEvent) {
diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
index 035a748..00bc751 100644
--- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
+++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
@@ -40,6 +40,13 @@
 
     @NonNull private final InputMethodManagerService mService;
 
+    /**
+     * The host input token of the input method that is currently associated with this controller.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    private IBinder mCurHostInputToken;
+
     private static final class CreateInlineSuggestionsRequest {
         @NonNull final InlineSuggestionsRequestInfo mRequestInfo;
         @NonNull final IInlineSuggestionsRequestCallback mCallback;
@@ -78,6 +85,17 @@
     }
 
     @GuardedBy("ImfLock.class")
+    void onResetSystemUi() {
+        mCurHostInputToken = null;
+    }
+
+    @Nullable
+    @GuardedBy("ImfLock.class")
+    IBinder getCurHostInputToken() {
+        return mCurHostInputToken;
+    }
+
+    @GuardedBy("ImfLock.class")
     void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
             InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
             boolean touchExplorationEnabled) {
@@ -124,8 +142,7 @@
                             mPendingInlineSuggestionsRequest.mCallback,
                             mPendingInlineSuggestionsRequest.mPackageName,
                             mService.getCurTokenDisplayIdLocked(),
-                            mService.getCurTokenLocked(),
-                            mService);
+                            mService.getCurTokenLocked());
             curMethod.onCreateInlineSuggestionsRequest(
                     mPendingInlineSuggestionsRequest.mRequestInfo, callback);
         } else {
@@ -161,22 +178,20 @@
      * The decorator which validates the host package name in the
      * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
      */
-    private static final class InlineSuggestionsRequestCallbackDecorator
+    private final class InlineSuggestionsRequestCallbackDecorator
             extends IInlineSuggestionsRequestCallback.Stub {
         @NonNull private final IInlineSuggestionsRequestCallback mCallback;
         @NonNull private final String mImePackageName;
         private final int mImeDisplayId;
         @NonNull private final IBinder mImeToken;
-        @NonNull private final InputMethodManagerService mImms;
 
         InlineSuggestionsRequestCallbackDecorator(
                 @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
-                int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms) {
+                int displayId, @NonNull IBinder imeToken) {
             mCallback = callback;
             mImePackageName = imePackageName;
             mImeDisplayId = displayId;
             mImeToken = imeToken;
-            mImms = imms;
         }
 
         @Override
@@ -195,7 +210,12 @@
                                 + "].");
             }
             request.setHostDisplayId(mImeDisplayId);
-            mImms.setCurHostInputToken(mImeToken, request.getHostInputToken());
+            synchronized (ImfLock.class) {
+                final IBinder curImeToken = mService.getCurTokenLocked();
+                if (mImeToken == curImeToken) {
+                    mCurHostInputToken = request.getHostInputToken();
+                }
+            }
             mCallback.onInlineSuggestionsRequest(request, callback);
         }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 1d048cb..e8543f2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -150,10 +150,11 @@
      *
      * @param sourceInputToken the source token.
      * @param displayId        the display hosting the IME window
+     * @param userId           the user ID this request is about
      * @return {@code true} if the transfer is successful
      */
     public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
-            int displayId);
+            int displayId, @UserIdInt int userId);
 
     /**
      * Reports that IME control has transferred to the given window token, or if null that
@@ -287,7 +288,7 @@
 
                 @Override
                 public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
-                        int displayId) {
+                        int displayId, @UserIdInt int userId) {
                     return false;
                 }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 25e2e3a..848f74e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -495,16 +495,6 @@
         return userData.mBindingController.getSequenceNumber();
     }
 
-    /**
-     * Increase the current binding sequence number by one.
-     * Reset to 1 on overflow.
-     */
-    @GuardedBy("ImfLock.class")
-    private void advanceSequenceNumberLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.advanceSequenceNumber();
-    }
-
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
@@ -583,16 +573,6 @@
     private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
 
     /**
-     * Set to true if our ServiceConnection is currently actively bound to
-     * a service (whether or not we have gotten its IBinder back yet).
-     */
-    @GuardedBy("ImfLock.class")
-    private boolean hasConnectionLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.hasMainConnection();
-    }
-
-    /**
      * The token tracking the current IME show request that is waiting for a connection to an IME,
      * otherwise {@code null}.
      */
@@ -645,14 +625,6 @@
     private int mCurTokenDisplayId = INVALID_DISPLAY;
 
     /**
-     * The host input token of the current active input method.
-     */
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    @MultiUserUnawareField
-    private IBinder mCurHostInputToken;
-
-    /**
      * The display ID of the input method indicates the fallback display which returned by
      * {@link #computeImeDisplayIdForTarget}.
      */
@@ -679,16 +651,6 @@
     }
 
     /**
-     * Time that we last initiated a bind to the input method, to determine
-     * if we should try to disconnect and reconnect to it.
-     */
-    @GuardedBy("ImfLock.class")
-    private long getLastBindTimeLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getLastBindTime();
-    }
-
-    /**
      * Have we called mCurMethod.bindInput()?
      */
     @MultiUserUnawareField
@@ -1840,21 +1802,6 @@
     }
 
     /**
-     * Sets current host input token.
-     *
-     * @param callerImeToken the token has been made for the current active input method
-     * @param hostInputToken the host input token of the current active input method
-     */
-    void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(callerImeToken)) {
-                return;
-            }
-            mCurHostInputToken = hostInputToken;
-        }
-    }
-
-    /**
      * Gets enabled subtypes of the specified {@link InputMethodInfo}.
      *
      * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}.
@@ -2170,7 +2117,8 @@
             @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags,
             @StartInputReason int startInputReason,
             int unverifiedTargetSdkVersion,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher,
+            @NonNull UserDataRepository.UserData userData) {
 
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
@@ -2211,7 +2159,8 @@
         final boolean connectionWasActive = mCurInputConnection != null;
 
         // Bump up the sequence for this client and attach it.
-        advanceSequenceNumberLocked();
+        userData.mBindingController.advanceSequenceNumber();
+
         mCurClient = cs;
         mCurInputConnection = inputConnection;
         mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
@@ -2233,7 +2182,6 @@
         if (connectionIsActive != connectionWasActive) {
             mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
         }
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
         final var bindingController = userData.mBindingController;
 
         // If configured, we want to avoid starting up the IME if it is not supposed to be showing
@@ -2271,7 +2219,7 @@
                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
             }
 
-            InputBindResult bindResult = tryReuseConnectionLocked(cs);
+            InputBindResult bindResult = tryReuseConnectionLocked(userData, cs);
             if (bindResult != null) {
                 return bindResult;
             }
@@ -2383,8 +2331,9 @@
 
     @GuardedBy("ImfLock.class")
     @Nullable
-    private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) {
-        if (hasConnectionLocked()) {
+    private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
+            @NonNull ClientState cs) {
+        if (userData.mBindingController.hasMainConnection()) {
             if (getCurMethodLocked() != null) {
                 // Return to client, and we will get back with it when
                 // we have had a session made for it.
@@ -2394,7 +2343,8 @@
                         InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
                         null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
             } else {
-                long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked();
+                final long lastBindTime = userData.mBindingController.getLastBindTime();
+                long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
                 if (bindingDuration < TIME_TO_RECONNECT) {
                     // In this case we have connected to the service, but
                     // don't yet have its interface.  If it hasn't been too
@@ -2527,7 +2477,7 @@
         mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
         updateSystemUiLocked(mImeWindowVis, mBackDisposition);
         mCurTokenDisplayId = INVALID_DISPLAY;
-        mCurHostInputToken = null;
+        mAutofillController.onResetSystemUi();
     }
 
     @GuardedBy("ImfLock.class")
@@ -3774,6 +3724,7 @@
                 startInputByWinGainedFocus, toolType);
         mVisibilityStateComputer.setWindowState(windowToken, windowState);
 
+        final var userData = mUserDataRepository.getOrCreate(userId);
         if (sameWindowFocused && isTextEditor) {
             if (DEBUG) {
                 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3784,7 +3735,7 @@
             if (editorInfo != null) {
                 return startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
-                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
+                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData);
             }
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
@@ -3816,7 +3767,7 @@
                         res = startInputUncheckedLocked(cs, inputContext,
                                 remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                                 startInputReason, unverifiedTargetSdkVersion,
-                                imeDispatcher);
+                                imeDispatcher, userData);
                         didStart = true;
                     }
                     break;
@@ -3831,7 +3782,6 @@
                 // Note that we can trust client's display ID as long as it matches
                 // to the display ID obtained from the window.
                 if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
-                    final var userData = mUserDataRepository.getOrCreate(userId);
                     userData.mBindingController.unbindCurrentMethod();
                 }
             }
@@ -3841,7 +3791,7 @@
                 res = startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                         startInputReason, unverifiedTargetSdkVersion,
-                        imeDispatcher);
+                        imeDispatcher, userData);
             } else {
                 res = InputBindResult.NULL_EDITOR_INFO;
             }
@@ -4476,6 +4426,7 @@
 
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
+            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
             final long token = proto.start(fieldId);
             proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
             proto.write(CUR_SEQ, getSequenceNumberLocked());
@@ -4494,7 +4445,7 @@
             proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
             proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
             proto.write(SYSTEM_READY, mSystemReady);
-            proto.write(HAVE_CONNECTION, hasConnectionLocked());
+            proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -5620,14 +5571,17 @@
 
         @Override
         public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
-                int displayId) {
+                int displayId, @UserIdInt int userId) {
             //TODO(b/150843766): Check if Input Token is valid.
             final IBinder curHostInputToken;
             synchronized (ImfLock.class) {
-                if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) {
+                if (displayId != mCurTokenDisplayId) {
                     return false;
                 }
-                curHostInputToken = mCurHostInputToken;
+                curHostInputToken = mAutofillController.getCurHostInputToken();
+                if (curHostInputToken == null) {
+                    return false;
+                }
             }
             return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken);
         }
@@ -5942,14 +5896,27 @@
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
             p.println("  mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
-            mImeBindingState.dump("  ", p);
+            mImeBindingState.dump(/* prefix= */ "  ", p);
             final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            p.println("  mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+            p.println("  mCurId=" + getCurIdLocked()
+                    + " mHaveConnection=" + userData.mBindingController.hasMainConnection()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
                     + userData.mBindingController.isVisibleBound());
+
+            p.println("  mUserDataRepository=");
+            // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
+            @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump =
+                    u -> {
+                        p.println("    mUserId=" + u.mUserId);
+                        p.println("      hasMainConnection="
+                                + u.mBindingController.hasMainConnection());
+                        p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
+                    };
+            mUserDataRepository.forAllUserData(userDataDump);
+
             p.println("  mCurToken=" + getCurTokenLocked());
             p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
-            p.println("  mCurHostInputToken=" + mCurHostInputToken);
+            p.println("  mCurHostInputToken=" + mAutofillController.getCurHostInputToken());
             p.println("  mCurIntent=" + getCurIntentLocked());
             method = getCurMethodLocked();
             p.println("  mCurMethod=" + getCurMethodLocked());
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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e814f17..21e4c96 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10862,8 +10862,10 @@
             final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
                     ? letterboxedContainerBounds
                     : task != null ? task.getBounds() : display.getBounds();
-            final int filledContainerRotation = task != null
-                    ? task.getConfiguration().windowConfiguration.getRotation()
+            final boolean useActivityRotation = container.hasFixedRotationTransform()
+                    && mIsInFixedOrientationOrAspectRatioLetterbox;
+            final int filledContainerRotation = useActivityRotation
+                    ? container.getWindowConfiguration().getRotation()
                     : display.getConfiguration().windowConfiguration.getRotation();
             final Point dimensions = getRotationZeroDimensions(
                     filledContainerBounds, filledContainerRotation);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 202e94c6..237003a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -184,6 +184,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -266,6 +267,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
@@ -7400,8 +7402,25 @@
         }
     }
 
+    /** Cache the return value for {@link #isPip2ExperimentEnabled()} */
+    private static Boolean sIsPip2ExperimentEnabled = null;
+
+    /**
+     * @return {@code true} if PiP2 implementation should be used. Besides the trunk stable flag,
+     * system property can be used to override this read only flag during development.
+     * It's currently limited to phone form factor, i.e., not enabled on ARC / TV.
+     */
     static boolean isPip2ExperimentEnabled() {
-        return Flags.enablePip2Implementation() || SystemProperties.getBoolean(
-                "wm_shell.pip2", false);
+        if (sIsPip2ExperimentEnabled == null) {
+            final FeatureInfo arcFeature = SystemConfig.getInstance().getAvailableFeatures().get(
+                    "org.chromium.arc");
+            final FeatureInfo tvFeature = SystemConfig.getInstance().getAvailableFeatures().get(
+                    FEATURE_LEANBACK);
+            final boolean isArc = arcFeature != null && arcFeature.version >= 0;
+            final boolean isTv = tvFeature != null && tvFeature.version >= 0;
+            sIsPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false)
+                    || (Flags.enablePip2Implementation() && !isArc && !isTv);
+        }
+        return sIsPip2ExperimentEnabled;
     }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index ce1a72d..b5af806 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,6 +19,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.DimenRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -266,10 +267,10 @@
     private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
 
     // Supplier for the value in pixel to consider when detecting vertical thin letterboxing
-    private final IntSupplier mThinLetterboxWidthFn;
+    private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier;
 
     // Supplier for the value in pixel to consider when detecting horizontal thin letterboxing
-    private final IntSupplier mThinLetterboxHeightFn;
+    private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier;
 
     // Allows to enable letterboxing strategy for translucent activities ignoring flags.
     private boolean mTranslucentLetterboxingOverrideEnabled;
@@ -307,6 +308,34 @@
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
     @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
+    // Cached version of IntSupplier customised to evaluate new dimen in pixels
+    // when density changes
+    private static class DimenPxIntSupplier implements IntSupplier {
+
+        @NonNull
+        private final Context mContext;
+
+        private final int mResourceId;
+
+        private float mLastDensity = Float.MIN_VALUE;
+        private int mValue = 0;
+
+        private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
+            mContext = context;
+            mResourceId = resourceId;
+        }
+
+        @Override
+        public int getAsInt() {
+            final float newDensity = mContext.getResources().getDisplayMetrics().density;
+            if (newDensity != mLastDensity) {
+                mLastDensity = newDensity;
+                mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
+            }
+            return mValue;
+        }
+    }
+
     LetterboxConfiguration(@NonNull final Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
@@ -364,9 +393,10 @@
                 R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
         mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
-        mThinLetterboxWidthFn = () ->  mContext.getResources().getDimensionPixelSize(
+
+        mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext,
                 R.dimen.config_letterboxThinLetterboxWidthDp);
-        mThinLetterboxHeightFn = () -> mContext.getResources().getDimensionPixelSize(
+        mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext,
                 R.dimen.config_letterboxThinLetterboxHeightDp);
 
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
@@ -1144,7 +1174,7 @@
      *         is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app.
      */
     int getThinLetterboxWidthPx() {
-        return mThinLetterboxWidthFn.getAsInt();
+        return mThinLetterboxWidthPxSupplier.getAsInt();
     }
 
     /**
@@ -1153,7 +1183,7 @@
      *         value for (H - h) / 2 to be considered for a thin letterboxed app.
      */
     int getThinLetterboxHeightPx() {
-        return mThinLetterboxHeightFn.getAsInt();
+        return mThinLetterboxHeightPxSupplier.getAsInt();
     }
 
     /**
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index e65b903..22c0f73 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -24,9 +24,11 @@
 namespace android {
 
 static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
-                                   jfloat requestedRefreshRate) {
-    ScopedUtfChars name(env, nameObj);
+                                   jstring uniqueIdStr, jfloat requestedRefreshRate) {
+    const ScopedUtfChars name(env, nameObj);
+    const ScopedUtfChars uniqueId(env, uniqueIdStr);
     sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
+                                                           std::string(uniqueId.c_str()),
                                                            requestedRefreshRate));
     return javaObjectForIBinder(env, token);
 }
@@ -178,7 +180,7 @@
 
 static const JNINativeMethod sDisplayMethods[] = {
         // clang-format off
-    {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;",
+    {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
             (void*)nativeDestroyDisplay },
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 62f5b89..a01c123 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -127,7 +127,7 @@
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
     jmethodID getInputPortAssociations;
-    jmethodID getInputUniqueIdAssociations;
+    jmethodID getInputUniqueIdAssociationsByPort;
     jmethodID getInputUniqueIdAssociationsByDescriptor;
     jmethodID getDeviceTypeAssociations;
     jmethodID getKeyboardLayoutAssociations;
@@ -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);
 }
@@ -616,10 +624,9 @@
         env->DeleteLocalRef(portAssociations);
     }
 
-    outConfig->uniqueIdAssociationsByPort =
-            readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
-                                                                 .getInputUniqueIdAssociations,
-                                                         "getInputUniqueIdAssociations");
+    outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+            std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
+                         "getInputUniqueIdAssociationsByPort");
 
     outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
             std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
@@ -735,7 +742,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 +773,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 +782,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 +802,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 +824,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 +1029,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 +1115,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 +1130,7 @@
     mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
 }
 
-void NativeInputManager::setFocusedDisplay(int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) {
     mInputManager->getDispatcher().setFocusedDisplay(displayId);
 }
 
@@ -1151,7 +1158,7 @@
     });
 }
 
-void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) {
     mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId);
 }
 
@@ -1176,7 +1183,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 +1194,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 +1334,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 +1348,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 +1403,7 @@
 }
 
 std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay(
-        int32_t associatedDisplayId) {
+        ui::LogicalDisplayId associatedDisplayId) {
     return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId);
 }
 
@@ -1469,9 +1478,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 +1599,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 +1631,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 +1652,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 +1720,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 +1886,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 +1896,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 +1943,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 +2036,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 +2105,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 +2129,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 +2499,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 +2507,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 +2526,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 +2664,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 +2682,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());
@@ -2930,8 +2945,8 @@
     GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
             "getInputPortAssociations", "()[Ljava/lang/String;");
 
-    GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
-                  "getInputUniqueIdAssociations", "()[Ljava/lang/String;");
+    GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz,
+                  "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;");
 
     GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz,
                   "getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;");
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/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 0c83e8e..2e126f1 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -295,7 +295,7 @@
         }
     }
 
-    private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
+    static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
         final int resolvedUserId = ActivityManager.handleIncomingUser(
                 Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, false,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 38ad5b6..b86db06 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.credentials;
 
+import static com.android.server.credentials.CredentialManagerService.getPrimaryProvidersForUserId;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -30,6 +32,7 @@
 import com.android.server.infra.AbstractPerUserSystemService;
 
 import java.util.List;
+import java.util.Set;
 
 
 /**
@@ -80,9 +83,12 @@
             Slog.i(TAG, "newServiceInfoLocked, mInfo null, "
                     + serviceComponent.flattenToString());
         }
+        Set<ComponentName> primaryProviders =
+                getPrimaryProvidersForUserId(mMaster.getContext(), mUserId);
         mInfo = CredentialProviderInfoFactory.create(
                 getContext(), serviceComponent,
-                mUserId, /*isSystemProvider=*/false);
+                mUserId, /*isSystemProvider=*/false,
+                primaryProviders.contains(serviceComponent));
         return mInfo.getServiceInfo();
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index b1673e2..7a026d5 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -67,6 +67,9 @@
 
     private final ResultReceiver mAutofillCallback;
 
+    @Nullable
+    private ComponentName mPrimaryProviderComponentName = null;
+
     public GetCandidateRequestSession(
             Context context, SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
@@ -104,8 +107,12 @@
         if (providerGetCandidateSessions != null) {
             Slog.d(TAG, "In startProviderSession - provider session created and "
                     + "being added for: " + providerInfo.getComponentName());
-            mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(),
-                    providerGetCandidateSessions);
+            ComponentName componentName = providerGetCandidateSessions
+                    .getComponentName();
+            if (providerInfo.isPrimary()) {
+                mPrimaryProviderComponentName = componentName;
+            }
+            mProviders.put(componentName.flattenToString(), providerGetCandidateSessions);
         }
         return providerGetCandidateSessions;
     }
@@ -138,7 +145,7 @@
 
         try {
             invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
-                    candidateProviderDataList, intent));
+                    candidateProviderDataList, intent, mPrimaryProviderComponentName));
         } catch (RemoteException e) {
             Slog.e(TAG, "Issue while responding to client with error : " + e);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9eb7b22..375fc5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21605,9 +21605,12 @@
                                 == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
             }
 
-            if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
-                    && isSingleUserMode && !mInjector.isChangeEnabled(
-                    PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
+            if (Flags.headlessSingleMinTargetSdk()
+                    && 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/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 622e702..54d101a 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -243,12 +243,12 @@
                                 return;
                             }
                             sSelfService.mIProfcollect.process();
-                            jobFinished(params, false);
                         } catch (RemoteException e) {
                             Log.e(LOG_TAG, "Failed to process profiles in background: "
                                     + e.getMessage());
                         }
                     });
+            jobFinished(params, false);
             return true;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 1666fef..54b2d4d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -282,7 +282,7 @@
             return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
                     new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
                         @Override
-                        public IBinder createDisplay(String name, boolean secure,
+                        public IBinder createDisplay(String name, boolean secure, String uniqueId,
                                 float requestedRefreshRate) {
                             return mMockDisplayToken;
                         }
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/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index b33a8aa..00c8ed1 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -53,7 +53,7 @@
     private IInputDevicesChangedListener mDevicesChangedListener;
     private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
             new HashMap<>();
-    private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+    private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort =
             new HashMap<>();
 
     InputManagerMockHelper(TestableLooper testableLooper,
@@ -79,11 +79,11 @@
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
         doAnswer(inv -> mDevices.get(inv.getArgument(0)))
                 .when(mIInputManagerMock).getInputDevice(anyInt());
-        doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0),
-                inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociation(
+        doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
+                inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
                         anyString(), anyString());
-        doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
-                mIInputManagerMock).removeUniqueIdAssociation(anyString());
+        doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when(
+                mIInputManagerMock).removeUniqueIdAssociationByPort(anyString());
 
         // Set a new instance of InputManager for testing that uses the IInputManager mock as the
         // interface to the server.
@@ -113,7 +113,7 @@
                 .setDescriptor(phys)
                 .setExternal(true)
                 .setAssociatedDisplayId(
-                        mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+                        mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys),
                                 Display.INVALID_DISPLAY))
                 .build();
 
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/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index b90fa21..79e401c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -18,15 +18,17 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-
 import static com.android.server.wm.testing.Assert.assertThrows;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -35,11 +37,13 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.testing.Assert;
+import com.android.internal.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -277,7 +281,7 @@
     }
 
     @Test
-    public void test_lettterboxPositionWhenReachabilityEnabledIsSet() {
+    public void test_letterboxPositionWhenReachabilityEnabledIsSet() {
         // Check that horizontal reachability is set with correct arguments
         mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
                 false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -344,4 +348,48 @@
         mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
         mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
     }
+
+    @Test
+    public void test_evaluateThinLetterboxWhenDensityChanges() {
+        final Resources rs = mock(Resources.class);
+        final DisplayMetrics dm = mock(DisplayMetrics.class);
+        final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class);
+        spyOn(mContext);
+        when(rs.getDisplayMetrics()).thenReturn(dm);
+        when(mContext.getResources()).thenReturn(rs);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+                .thenReturn(100);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+                .thenReturn(200);
+        final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp);
+
+        // Verify the values are the expected ones
+        dm.density = 100;
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+                .thenReturn(100);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+                .thenReturn(200);
+        final int thinWidthPx = configuration.getThinLetterboxWidthPx();
+        final int thinHeightPx = configuration.getThinLetterboxHeightPx();
+        assertEquals(100, thinWidthPx);
+        assertEquals(200, thinHeightPx);
+
+        // We change the values in the resources but not the update condition (density) and the
+        // result should not change
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+                .thenReturn(300);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+                .thenReturn(400);
+        final int thinWidthPx2 = configuration.getThinLetterboxWidthPx();
+        final int thinHeightPx2 = configuration.getThinLetterboxHeightPx();
+        assertEquals(100, thinWidthPx2);
+        assertEquals(200, thinHeightPx2);
+
+        // We update the condition (density) so the new resource values should be read
+        dm.density = 150;
+        final int thinWidthPx3 = configuration.getThinLetterboxWidthPx();
+        final int thinHeightPx3 = configuration.getThinLetterboxHeightPx();
+        assertEquals(300, thinWidthPx3);
+        assertEquals(400, thinHeightPx3);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index afa6698..d9fd312 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -1894,11 +1894,7 @@
     public void testApplyTransaction_createTaskFragmentDecorSurface() {
         mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
 
-        // TODO(b/293654166) remove system organizer requirement once security review is cleared.
-        mController.unregisterOrganizer(mIOrganizer);
-        registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
         final Task task = createTask(mDisplayContent);
-
         final TaskFragment tf = createTaskFragment(task);
         final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                 OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build();
@@ -1913,9 +1909,6 @@
     public void testApplyTransaction_removeTaskFragmentDecorSurface() {
         mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
 
-        // TODO(b/293654166) remove system organizer requirement once security review is cleared.
-        mController.unregisterOrganizer(mIOrganizer);
-        registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
         final Task task = createTask(mDisplayContent);
         final TaskFragment tf = createTaskFragment(task);
 
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 709f58d..3c72498 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -311,9 +311,8 @@
         verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
     }
 
-    // TODO(b/324075859): Rename this method to addUniqueIdAssociationByPort_verifyAssociations
     @Test
-    fun addUniqueIdAssociation_verifyAssociations() {
+    fun addUniqueIdAssociationByPort_verifyAssociations() {
         // Overall goal is to have 2 displays and verify that events from the InputDevice are
         // sent only to the view that is on the associated display.
         // So, associate the InputDevice with display 1, then send and verify KeyEvents.
@@ -334,7 +333,7 @@
         val inputDevice = createInputDevice()
 
         // Associate input device with display
-        service.addUniqueIdAssociation(
+        service.addUniqueIdAssociationByPort(
                 inputDevice.name,
                 virtualDisplays[0].display.displayId.toString()
         )
@@ -358,10 +357,10 @@
         verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
 
         // Remove association
-        service.removeUniqueIdAssociation(inputDevice.name)
+        service.removeUniqueIdAssociationByPort(inputDevice.name)
 
         // Associate with Display 2
-        service.addUniqueIdAssociation(
+        service.addUniqueIdAssociationByPort(
                 inputDevice.name,
                 virtualDisplays[1].display.displayId.toString()
         )
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);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 3d5a0f7..395f744 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -21,6 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.net.wifi.flags.Flags;
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -170,7 +171,7 @@
          * @return Returns the Builder object.
          */
         @NonNull
-        @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
+        @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS)
         public Builder setBatteryCharging(boolean isBatteryCharging) {
             mIsBatteryCharging = isBatteryCharging;
             return this;
@@ -285,7 +286,7 @@
      *
      * @return Returns true if the battery of the remote device is charging.
      */
-    @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
+    @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS)
     public boolean isBatteryCharging() {
         return mIsBatteryCharging;
     }