Merge "Switch to -nodeps flavor of mockito-kotlin" 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/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 6da96c1..3d6ec19 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -313,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/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 9a63394..49ab15a 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -429,10 +429,9 @@
                         "Lazy values ref count below 0");
                 // No more lazy values in mMap, so we can recycle the parcel early rather than
                 // waiting for the next GC run
-                if (mLazyValues == 0) {
-                    Preconditions.checkState(mWeakParcelledData.get() != null,
-                            "Parcel recycled earlier than expected");
-                    recycleParcel(mWeakParcelledData.get());
+                Parcel parcel = mWeakParcelledData.get();
+                if (mLazyValues == 0 && parcel != null) {
+                    recycleParcel(parcel);
                     mWeakParcelledData = null;
                 }
             }
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/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 7bceb2c..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.
      *
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/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/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/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 30bec77..c2a83b1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -68,6 +68,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -1442,14 +1443,8 @@
         int stringRes = R.string.bluetooth_pairing;
         //when profile is connected, information would be available
         if (profileConnected) {
-            // Update Meta data for connected device
-            if (BluetoothUtils.getBooleanMetaData(
-                    mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
-                leftBattery = BluetoothUtils.getIntMetaData(mDevice,
-                        BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
-                rightBattery = BluetoothUtils.getIntMetaData(mDevice,
-                        BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
-            }
+            leftBattery = getLeftBatteryLevel();
+            rightBattery = getRightBatteryLevel();
 
             // Set default string with battery level in device connected situation.
             if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
@@ -1485,7 +1480,7 @@
                 boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
                         && isConnectedHapClientDevice();
                 if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) {
-                    return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary);
+                    stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary);
                 }
             }
         }
@@ -1498,6 +1493,8 @@
         boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level
                 || stringRes == R.string.bluetooth_active_battery_level
                 || stringRes == R.string.bluetooth_active_battery_level_untethered
+                || stringRes == R.string.bluetooth_active_battery_level_untethered_left
+                || stringRes == R.string.bluetooth_active_battery_level_untethered_right
                 || stringRes == R.string.bluetooth_battery_level_untethered;
         if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) {
             return getTvBatterySummary(
@@ -1510,6 +1507,14 @@
         if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
             return mContext.getString(stringRes, Utils.formatPercentage(leftBattery),
                     Utils.formatPercentage(rightBattery));
+        } else if (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+                && !BluetoothUtils.getBooleanMetaData(mDevice,
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            return mContext.getString(stringRes, Utils.formatPercentage(leftBattery));
+        } else if (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+                && !BluetoothUtils.getBooleanMetaData(mDevice,
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            return mContext.getString(stringRes, Utils.formatPercentage(rightBattery));
         } else {
             return mContext.getString(stringRes, batteryLevelPercentageString);
         }
@@ -1553,60 +1558,34 @@
         return spannableBuilder;
     }
 
-    private CharSequence getHearingDeviceSummary(int leftBattery, int rightBattery,
+    private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery,
             boolean shortSummary) {
+        boolean isLeftDeviceConnected = getConnectedHearingAidSide(
+                HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent();
+        boolean isRightDeviceConnected = getConnectedHearingAidSide(
+                HearingAidInfo.DeviceSide.SIDE_RIGHT).isPresent();
+        boolean shouldShowLeftBattery =
+                !shortSummary && (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+        boolean shouldShowRightBattery =
+                !shortSummary && (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
 
-        CachedBluetoothDevice memberDevice = getMemberDevice().stream().filter(
-                CachedBluetoothDevice::isConnected).findFirst().orElse(null);
-        if (memberDevice == null && mSubDevice != null && mSubDevice.isConnected()) {
-            memberDevice = mSubDevice;
+        if (isLeftDeviceConnected && isRightDeviceConnected) {
+            return (shouldShowLeftBattery && shouldShowRightBattery)
+                    ? R.string.bluetooth_active_battery_level_untethered
+                    : R.string.bluetooth_hearing_aid_left_and_right_active;
+        }
+        if (isLeftDeviceConnected) {
+            return shouldShowLeftBattery
+                    ? R.string.bluetooth_active_battery_level_untethered_left
+                    : R.string.bluetooth_hearing_aid_left_active;
+        }
+        if (isRightDeviceConnected) {
+            return shouldShowRightBattery
+                    ? R.string.bluetooth_active_battery_level_untethered_right
+                    : R.string.bluetooth_hearing_aid_right_active;
         }
 
-        CachedBluetoothDevice leftDevice = null;
-        CachedBluetoothDevice rightDevice = null;
-        final int deviceSide = getDeviceSide();
-        if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
-            leftDevice = this;
-            rightDevice = memberDevice;
-        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
-            leftDevice = memberDevice;
-            rightDevice = this;
-        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
-            leftDevice = this;
-            rightDevice = this;
-        }
-
-        if (leftBattery < 0 && leftDevice != null) {
-            leftBattery = leftDevice.getBatteryLevel();
-        }
-        if (rightBattery < 0 && rightDevice != null) {
-            rightBattery = rightDevice.getBatteryLevel();
-        }
-
-        if (leftDevice != null && rightDevice != null) {
-            if (leftBattery >= 0 && rightBattery >= 0 && !shortSummary) {
-                return mContext.getString(R.string.bluetooth_active_battery_level_untethered,
-                        Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery));
-            } else {
-                return mContext.getString(R.string.bluetooth_hearing_aid_left_and_right_active);
-            }
-        } else if (leftDevice != null) {
-            if (leftBattery >= 0 && !shortSummary) {
-                return mContext.getString(R.string.bluetooth_active_battery_level_untethered_left,
-                        Utils.formatPercentage(leftBattery));
-            } else {
-                return mContext.getString(R.string.bluetooth_hearing_aid_left_active);
-            }
-        } else if (rightDevice != null) {
-            if (rightBattery >= 0 && !shortSummary) {
-                return mContext.getString(R.string.bluetooth_active_battery_level_untethered_right,
-                        Utils.formatPercentage(rightBattery));
-            } else {
-                return mContext.getString(R.string.bluetooth_hearing_aid_right_active);
-            }
-        }
-
-        return mContext.getString(R.string.bluetooth_active_no_battery_level);
+        return R.string.bluetooth_active_no_battery_level;
     }
 
     private void addBatterySpan(SpannableStringBuilder builder,
@@ -1632,6 +1611,56 @@
         return leftBattery >= 0 && rightBattery >= 0;
     }
 
+    private Optional<CachedBluetoothDevice> getConnectedHearingAidSide(
+            @HearingAidInfo.DeviceSide int side) {
+        return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream())
+                .filter(Objects::nonNull)
+                .filter(device -> device.getDeviceSide() == side
+                        || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT)
+                .filter(device -> device.getDevice().isConnected())
+                // For hearing aids, we should expect only one device assign to one side, but if
+                // it happens, we don't care which one.
+                .findAny();
+    }
+
+    private int getLeftBatteryLevel() {
+        int leftBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        if (BluetoothUtils.getBooleanMetaData(mDevice,
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            leftBattery = BluetoothUtils.getIntMetaData(mDevice,
+                    BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+        }
+
+        // Retrieve hearing aids (ASHA, HAP) individual side battery level
+        if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+                    .map(CachedBluetoothDevice::getBatteryLevel)
+                    .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+                    .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+        }
+
+        return leftBattery;
+    }
+
+    private int getRightBatteryLevel() {
+        int rightBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        if (BluetoothUtils.getBooleanMetaData(
+                mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            rightBattery = BluetoothUtils.getIntMetaData(mDevice,
+                    BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+        }
+
+        // Retrieve hearing aids (ASHA, HAP) individual side battery level
+        if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
+                    .map(CachedBluetoothDevice::getBatteryLevel)
+                    .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+                    .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+        }
+
+        return rightBattery;
+    }
+
     private boolean isProfileConnectedFail() {
         Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress()
                 + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b4bd482..b9bf9ca 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -753,33 +753,71 @@
     }
 
     @Test
-    public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() {
+    public void getConnectionSummary_testHearingAidLeftEarBatteryNotInCall_returnActiveBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: Normal (Without In Call)
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
+        //   Get "Active. L: 10% battery." result with Battery Level 10.
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery.");
     }
 
     @Test
-    public void getTvConnectionSummary_testHearingAidBatteryWithoutInCall_returnBattery() {
+    public void getTvConnectionSummary_testHearingAidLeftEarBatteryWithoutInCall_returnBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: Normal (Without In Call)
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%");
+        //   Get "Left: 10% battery" result with Battery Level 10.
+        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
+                "Left: 10% battery");
+    }
+
+    @Test
+    public void getConnectionSummary_testHearingAidLeftEarBatteryInCall_returnActiveBattery() {
+        // Arrange:
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
+        //   2. Battery Level: 10
+        //   3. Audio Manager: In Call
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+        mBatteryLevel = 10;
+
+        // Act & Assert:
+        //   Get "Active. L: 10% battery." result with Battery Level 10.
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery.");
+    }
+
+    @Test
+    public void getTvConnectionSummary_testHearingAidLeftEarBatteryInCall_returnBattery() {
+        // Arrange:
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
+        //   2. Battery Level: 10
+        //   3. Audio Manager: In Call
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+        mBatteryLevel = 10;
+
+        // Act & Assert:
+        //   Get "Left: 10% battery" result with Battery Level 10.
+        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
+                "Left: 10% battery");
     }
 
     @Test
@@ -851,35 +889,45 @@
     }
 
     @Test
-    public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() {
+    public void getConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: In Call
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setSubDevice(mSubCachedDevice);
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
+        //   Get "Active. L: 10%, R: 10% battery." result with Battery Level 10.
+        assertThat(mCachedDevice.getConnectionSummary().toString())
+                .isEqualTo("Active. L: 10%, R: 10% battery.");
     }
 
     @Test
-    public void getTvConnectionSummary_testHearingAidBatteryInCall_returnBattery() {
+    public void getTvConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: In Call
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setSubDevice(mSubCachedDevice);
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%");
+        //   Get "Left: 10% battery Right: 10% battery" result with Battery Level 10.
+        assertThat(mCachedDevice.getTvConnectionSummary().toString())
+                .isEqualTo("Left: 10% battery Right: 10% battery");
     }
 
     @Test
@@ -1151,7 +1199,7 @@
         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                 "true".getBytes());
         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
@@ -1160,7 +1208,7 @@
                 TWS_BATTERY_RIGHT.getBytes());
 
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
-                "Active, L: 15% battery, R: 25% battery");
+                "Active. L: 15%, R: 25% battery.");
     }
 
     @Test
@@ -1169,7 +1217,7 @@
         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                 "true".getBytes());
         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
@@ -1178,7 +1226,7 @@
                 TWS_BATTERY_RIGHT.getBytes());
 
         assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
-                "Left 15% Right 25%");
+                "Left: 15% battery Right: 25% battery");
     }
 
     @Test
@@ -1741,16 +1789,6 @@
                 BluetoothProfile.STATE_CONNECTED);
     }
 
-    private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
-        doReturn(status).when(profile).getConnectionStatus(mDevice);
-        mCachedDevice.onProfileStateChanged(profile, status);
-    }
-
-    private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
-        doReturn(status).when(profile).getConnectionStatus(mSubDevice);
-        mSubCachedDevice.onProfileStateChanged(profile, status);
-    }
-
     @Test
     public void getSubDevice_setSubDevice() {
         mCachedDevice.setSubDevice(mSubCachedDevice);
@@ -2030,6 +2068,29 @@
         assertThat(mCachedDevice.getConnectionSummary(false)).isNull();
     }
 
+    private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
+        doReturn(status).when(profile).getConnectionStatus(mDevice);
+        mCachedDevice.onProfileStateChanged(profile, status);
+        updateConnectionStatus(mCachedDevice);
+    }
+
+    private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
+        doReturn(status).when(profile).getConnectionStatus(mSubDevice);
+        mSubCachedDevice.onProfileStateChanged(profile, status);
+        updateConnectionStatus(mSubCachedDevice);
+    }
+
+    private void updateConnectionStatus(CachedBluetoothDevice cachedBluetoothDevice) {
+        for (LocalBluetoothProfile profile : cachedBluetoothDevice.getProfiles()) {
+            int status = cachedBluetoothDevice.getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTED) {
+                when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(true);
+                return;
+            }
+        }
+        when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(false);
+    }
+
     private HearingAidInfo getLeftAshaHearingAidInfo() {
         return new HearingAidInfo.Builder()
                 .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
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/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
index 556bbbe..c37d626 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -18,9 +18,11 @@
 
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -33,20 +35,15 @@
 constructor(
     private val mediaCarouselController: MediaCarouselController,
     @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
-    private val mediaCarouselViewModel: MediaCarouselViewModel,
+    private val keyguardMediaViewModel: KeyguardMediaViewModel,
 ) {
 
-    private fun isVisible(): Boolean {
-        if (mediaCarouselController.mediaFrame == null) {
-            return false
-        }
-        return mediaCarouselViewModel.isMediaVisible
-    }
-
     @Composable
     fun SceneScope.KeyguardMediaCarousel() {
+        val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsState()
+
         MediaCarousel(
-            isVisible = ::isVisible,
+            isVisible = isMediaVisible,
             mediaHost = mediaHost,
             modifier = Modifier.fillMaxWidth(),
             carouselController = mediaCarouselController,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 241c171..581f3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -42,12 +42,12 @@
 
 @Composable
 fun SceneScope.MediaCarousel(
-    isVisible: () -> Boolean,
+    isVisible: Boolean,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
     carouselController: MediaCarouselController,
 ) {
-    if (!isVisible()) {
+    if (!isVisible) {
         return
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5f84dd4..2f241ce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -181,6 +181,7 @@
     val horizontalPadding = dimensionResource(R.dimen.qs_content_horizontal_padding)
     Row(
         modifier
+            .sysuiResTag("qs_footer_actions")
             .fillMaxWidth()
             .graphicsLayer { this.alpha = alpha }
             .then(backgroundModifier)
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 30b6c6c..7c415cc 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
@@ -288,7 +288,8 @@
                     }
 
                 Column(
-                    modifier = shadeHeaderAndQuickSettingsModifier,
+                    modifier =
+                        shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"),
                 ) {
                     when (LocalWindowSizeClass.current.widthSizeClass) {
                         WindowWidthSizeClass.Compact ->
@@ -336,11 +337,13 @@
                         viewModel.qsSceneAdapter,
                         { viewModel.qsSceneAdapter.qsHeight },
                         isSplitShade = false,
-                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
+                        modifier = Modifier.sysuiResTag("quick_settings_panel")
                     )
 
+                    val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+
                     MediaCarousel(
-                        isVisible = viewModel::isMediaVisible,
+                        isVisible = isMediaVisible,
                         mediaHost = mediaHost,
                         modifier = Modifier.fillMaxWidth(),
                         carouselController = mediaCarouselController,
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 10fe0cab..d2e7d7d 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
@@ -221,6 +221,7 @@
             canOverflow = false
         )
     val isClickable by viewModel.isClickable.collectAsState()
+    val isMediaVisible by viewModel.isMediaVisible.collectAsState()
 
     val shouldPunchHoleBehindScrim =
         layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
@@ -274,7 +275,7 @@
                             }
 
                             MediaCarousel(
-                                isVisible = viewModel::isMediaVisible,
+                                isVisible = isMediaVisible,
                                 mediaHost = mediaHost,
                                 modifier = Modifier.fillMaxWidth(),
                                 carouselController = mediaCarouselController,
@@ -381,6 +382,8 @@
     viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
     DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
 
+    val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+
     val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
 
     Box(
@@ -455,7 +458,7 @@
                             }
 
                             MediaCarousel(
-                                isVisible = viewModel::isMediaVisible,
+                                isVisible = isMediaVisible,
                                 mediaHost = mediaHost,
                                 modifier = Modifier.fillMaxWidth(),
                                 carouselController = mediaCarouselController,
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/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/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index feb7298..7292985 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.fakeUserTracker
@@ -64,7 +63,7 @@
 
     private val kosmos = testKosmos()
 
-    private lateinit var underTest: HomeControlsComponentInteractor
+    private val underTest by lazy { kosmos.homeControlsComponentInteractor }
 
     @Before
     fun setUp() =
@@ -73,8 +72,7 @@
             fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
             whenever(controlsComponent.getControlsListingController())
                 .thenReturn(Optional.of(controlsListingController))
-
-            underTest = homeControlsComponentInteractor
+            Unit
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index da60c18..dfc004a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -30,10 +30,10 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
 import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -47,11 +47,11 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CustomTileRepositoryTest : SysuiTestCase() {
 
-    private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+    private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
     private val underTest: CustomTileRepository =
         with(kosmos) {
             CustomTileRepositoryImpl(
-                tileSpec,
+                customTileSpec,
                 customTileStatePersister,
                 packageManagerAdapterFacade.packageManagerAdapter,
                 testScope.testScheduler,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
index a5c5544..a29289a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -38,8 +38,8 @@
 import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
 import com.android.systemui.qs.tiles.impl.custom.customTileRepository
 import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.user.data.repository.userRepository
@@ -60,12 +60,12 @@
     private val kosmos =
         testKosmos().apply {
             componentName = TEST_COMPONENT
-            tileSpec = TileSpec.create(componentName)
+            customTileSpec = TileSpec.create(componentName)
         }
     private val underTest =
         with(kosmos) {
             CustomTileDataInteractor(
-                tileSpec = tileSpec,
+                tileSpec = customTileSpec,
                 defaultsRepository = customTileDefaultsRepository,
                 serviceInteractor = customTileServiceInteractor,
                 customTileInteractor = customTileInteractor,
@@ -180,7 +180,7 @@
                 setup()
                 customTileDefaultsRepository.putDefaults(
                     TEST_USER_1.userHandle,
-                    tileSpec.componentName,
+                    customTileSpec.componentName,
                     CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
                 )
 
@@ -198,7 +198,7 @@
                 setup()
                 customTileDefaultsRepository.putDefaults(
                     TEST_USER_1.userHandle,
-                    tileSpec.componentName,
+                    customTileSpec.componentName,
                     CustomTileDefaults.Error,
                 )
 
@@ -216,7 +216,7 @@
                 setup()
                 customTileDefaultsRepository.putDefaults(
                     TEST_USER_2.userHandle,
-                    tileSpec.componentName,
+                    customTileSpec.componentName,
                     CustomTileDefaults.Error,
                 )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 9546a32..33299d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -31,9 +31,9 @@
 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,12 +50,12 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CustomTileInteractorTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+    private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
 
     private val underTest: CustomTileInteractor =
         with(kosmos) {
             CustomTileInteractor(
-                tileSpec = tileSpec,
+                tileSpec = customTileSpec,
                 defaultsRepository = customTileDefaultsRepository,
                 customTileRepository = customTileRepository,
                 tileScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index a2127a4..3972938 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -33,9 +33,9 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
@@ -51,7 +51,8 @@
 class CustomTileMapperTest : SysuiTestCase() {
 
     private val uriGrantsManager: IUriGrantsManager = mock {}
-    private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+    private val kosmos =
+        testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
     private val underTest by lazy {
         CustomTileMapper(
             context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
@@ -202,7 +203,7 @@
     ) =
         CustomTileDataModel(
             UserHandle.of(1),
-            tileSpec.componentName,
+            customTileSpec.componentName,
             Tile().apply {
                 state = tileState
                 label = "test label"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
index c709f16..72e5766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -44,9 +44,9 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
 import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
@@ -68,7 +68,7 @@
     private val kosmos =
         testKosmos().apply {
             componentName = TEST_COMPONENT
-            tileSpec = TileSpec.create(componentName)
+            customTileSpec = TileSpec.create(componentName)
             testCase = this@CustomTileUserActionInteractorTest
         }
 
@@ -79,7 +79,7 @@
                     mock {
                         whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
                     },
-                tileSpec = tileSpec,
+                tileSpec = customTileSpec,
                 qsTileLogger = qsTileLogger,
                 windowManager = windowManagerFacade.windowManager,
                 displayTracker = mock {},
@@ -227,7 +227,7 @@
     private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
 
     private fun Kosmos.customTileModel(
-        componentName: ComponentName = tileSpec.componentName,
+        componentName: ComponentName = customTileSpec.componentName,
         activityLaunchForClick: PendingIntent? = null,
         tileState: Int = 111,
     ) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 179ba42..0b55bef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -35,7 +35,10 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
@@ -100,7 +103,7 @@
                 footerActionsViewModelFactory = footerActionsViewModelFactory,
                 footerActionsController = footerActionsController,
                 sceneBackInteractor = sceneBackInteractor,
-                mediaDataManager = mediaDataManager,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
             )
     }
 
@@ -236,20 +239,34 @@
     }
 
     @Test
-    fun hasMedia_mediaVisible() {
+    fun addAndRemoveMedia_mediaVisibilityIsUpdated() =
         testScope.runTest {
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = true)
 
-            assertThat(underTest.isMediaVisible()).isTrue()
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
+
+            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+            assertThat(isMediaVisible).isFalse()
         }
-    }
 
     @Test
-    fun doesNotHaveMedia_mediaNotVisible() {
+    fun addInactiveMedia_mediaVisibilityIsUpdated() =
         testScope.runTest {
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = false)
 
-            assertThat(underTest.isMediaVisible()).isFalse()
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
         }
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 4cb8bf8..9e7e766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -209,7 +210,7 @@
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
-                mediaDataManager = mediaDataManager,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
                 footerActionsController = kosmos.footerActionsController,
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 2439217..4622f0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -32,7 +32,10 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
@@ -50,7 +53,6 @@
 import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -94,7 +96,7 @@
                 qsSceneAdapter = qsSceneAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
-                mediaDataManager = mediaDataManager,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
                 footerActionsController = kosmos.footerActionsController,
@@ -222,19 +224,20 @@
         }
 
     @Test
-    fun hasActiveMedia_mediaVisible() =
+    fun addAndRemoveMedia_mediaVisibilityisUpdated() =
         testScope.runTest {
-            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = true)
 
-            assertThat(underTest.isMediaVisible()).isTrue()
-        }
+            assertThat(isMediaVisible).isFalse()
 
-    @Test
-    fun doesNotHaveActiveMedia_mediaNotVisible() =
-        testScope.runTest {
-            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
 
-            assertThat(underTest.isMediaVisible()).isFalse()
+            assertThat(isMediaVisible).isTrue()
+
+            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+            assertThat(isMediaVisible).isFalse()
         }
 
     @Test
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
index cf7a730..a30a122 100644
--- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -13,12 +13,16 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <!-- Since this layer list has just one layer, we can remove it and replace with the inner
-         layer drawable. However this should only be done when the flag
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/qs_tile_ripple_color">
+    <!-- We don't really use the ripple effect here, but changing it to LayerDrawable causes
+         performance regression, see: b/339412453.
+         Since this ripple has just one layer inside, we can try to remove that extra "background"
+         layer. However this should only be done when the flag
          com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
          fully replaces the previous one to ensure consistency with code sections searching for
-         specific ids in drawable hierarchy -->
+         specific ids in drawable hierarchy 
+         -->
     <item
         android:id="@id/background">
         <layer-list>
@@ -48,4 +52,4 @@
             </item>
         </layer-list>
     </item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
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/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/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/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 40d7440..b27fcfc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -126,7 +126,7 @@
 
     private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
         getSharedPrefsForUser(user)
-            .observe(CTA_DISMISSED_STATE)
+            .observe()
             // Emit at the start of collection to ensure we get an initial value
             .onStart { emit(Unit) }
             .map { getCtaDismissedState() }
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/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 7c2dae3..060a331 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -25,10 +25,10 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 
 class AuthorizedPanelsRepositoryImpl
 @Inject
@@ -40,7 +40,7 @@
 
     override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
         val prefs = instantiateSharedPrefs(user)
-        return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+        return prefs.observe().emitOnStart().map { getAuthorizedPanelsInternal(prefs) }
     }
 
     override fun getAuthorizedPanels(): Set<String> {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 9be04940..691ec76 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -26,12 +26,12 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -63,8 +63,8 @@
     ): Flow<SelectedComponentRepository.SelectedComponent?> {
         val prefs = getSharedPreferencesForUser(userHandle.identifier)
         return prefs
-            .observe(PREF_COMPONENT)
-            .onStart { emit(Unit) }
+            .observe()
+            .emitOnStart()
             .map { getSelectedComponent(userHandle) }
             .flowOn(bgDispatcher)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 0fd6887..8c3de4b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -89,8 +89,8 @@
                 dozeFalsingManagerAdapter,
                 dozeTriggers,
                 dozeUi,
-                dozeScreenState,
                 dozeScreenBrightness,
+                dozeScreenState,
                 dozeWallpaperState,
                 dozeDockHandler,
                 dozeAuthRemover,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 74452d1..2034138 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -21,7 +21,6 @@
 import android.content.ComponentName
 import android.os.PowerManager
 import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.domain.interactor.PackageChangeInteractor
 import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.controls.ControlsServiceInfo
@@ -36,6 +35,7 @@
 import com.android.systemui.util.kotlin.pairwiseBy
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.time.SystemClock
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
 import kotlin.math.abs
 import kotlin.time.Duration.Companion.milliseconds
@@ -132,7 +132,7 @@
                         ?: panels.firstOrNull()
                 item?.panelActivity
             }
-            .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+            .stateIn(bgScope, SharingStarted.Eagerly, null)
 
     private val taskFragmentFinished =
         MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
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/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
index bf22563..e68e465 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+class KeyguardMediaViewModel @Inject constructor(mediaCarouselInteractor: MediaCarouselInteractor) {
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index e3ba36f..0696fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -16,8 +16,17 @@
 
 package com.android.systemui.qs.panels.dagger
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
 import com.android.systemui.qs.panels.data.repository.IconTilesRepository
 import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.ui.compose.GridLayout
@@ -31,8 +40,23 @@
 interface PanelsModule {
     @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
 
+    @Binds
+    fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
+
+    @Binds
+    fun bindDefaultGridConsistencyInteractor(
+        impl: NoopGridConsistencyInteractor
+    ): GridTypeConsistencyInteractor
+
     companion object {
         @Provides
+        @SysUISingleton
+        @GridConsistencyLog
+        fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer {
+            return factory.create("GridConsistencyLog", 50)
+        }
+
+        @Provides
         @IntoSet
         fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
             return Pair(InfiniteGridLayoutType, gridLayout)
@@ -44,5 +68,20 @@
         ): Map<GridLayoutType, GridLayout> {
             return entries.toMap()
         }
+
+        @Provides
+        @IntoSet
+        fun provideGridConsistencyInteractor(
+            consistencyInteractor: InfiniteGridConsistencyInteractor
+        ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+            return Pair(InfiniteGridLayoutType, consistencyInteractor)
+        }
+
+        @Provides
+        fun provideGridConsistencyInteractorMap(
+            entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
+        ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
+            return entries.toMap()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 02dd33e..542d0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -20,10 +20,16 @@
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+interface GridLayoutTypeRepository {
+    val layout: StateFlow<GridLayoutType>
+}
 
 @SysUISingleton
-class GridLayoutTypeRepository @Inject constructor() {
-    val layout: Flow<GridLayoutType> = flowOf(InfiniteGridLayoutType)
+class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
+    private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
+    override val layout = _layout.asStateFlow()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
index 92f87e7..e581bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -19,20 +19,20 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
 interface IconTilesRepository {
-    val iconTilesSpecs: Flow<Set<TileSpec>>
+    val iconTilesSpecs: StateFlow<Set<TileSpec>>
 }
 
 @SysUISingleton
 class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
 
-    /** Set of toggleable tiles that are suitable for being shown as an icon. */
-    override val iconTilesSpecs: Flow<Set<TileSpec>> =
-        flowOf(
+    private val _iconTilesSpecs =
+        MutableStateFlow(
             setOf(
                 TileSpec.create("airplane"),
                 TileSpec.create("battery"),
@@ -50,4 +50,7 @@
                 TileSpec.create("rotation")
             )
         )
+
+    /** Set of toggleable tiles that are suitable for being shown as an icon. */
+    override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
new file mode 100644
index 0000000..43ccdf66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class InfiniteGridSizeRepository @Inject constructor() {
+    // Number of columns in the narrowest state for consistency
+    private val _columns = MutableStateFlow(4)
+    val columns: StateFlow<Int> = _columns.asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
new file mode 100644
index 0000000..7732092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.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.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class GridConsistencyInteractor
+@Inject
+constructor(
+    private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val consistencyInteractors:
+        Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
+    private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
+    @GridConsistencyLog private val logBuffer: LogBuffer,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    fun start() {
+        applicationScope.launch {
+            gridLayoutTypeInteractor.layout.collectLatest { type ->
+                val consistencyInteractor =
+                    consistencyInteractors[type] ?: defaultConsistencyInteractor
+                currentTilesInteractor.currentTiles
+                    .map { tiles -> tiles.map { it.spec } }
+                    .collectLatest { tiles ->
+                        val newTiles = consistencyInteractor.reconcileTiles(tiles)
+                        if (newTiles != tiles) {
+                            currentTilesInteractor.setTiles(newTiles)
+                            logChange(newTiles)
+                        }
+                    }
+            }
+        }
+    }
+
+    private fun logChange(tiles: List<TileSpec>) {
+        logBuffer.log(
+            LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
+            LogLevel.DEBUG,
+            { str1 = tiles.toString() },
+            { "Tiles reordered: $str1" }
+        )
+    }
+
+    private companion object {
+        const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
index bf22563..4cdabae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.qs.pipeline.shared.TileSpec
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+interface GridTypeConsistencyInteractor {
+    /**
+     * Given a list of tiles, return the best list of the same tiles (preserving as much order as
+     * possible, such that it's consistent with the current layout.
+     */
+    fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 1aec193..ccc1c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -20,10 +20,10 @@
 import com.android.systemui.qs.panels.data.repository.IconTilesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
 @SysUISingleton
 class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) {
-    val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs
+    val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
new file mode 100644
index 0000000..74e906c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class InfiniteGridConsistencyInteractor
+@Inject
+constructor(
+    private val iconTilesInteractor: IconTilesInteractor,
+    private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridTypeConsistencyInteractor {
+
+    /**
+     * Tries to fill in every columns of all rows (except the last row), potentially reordering
+     * tiles.
+     */
+    override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
+        val newTiles: MutableList<TileSpec> = mutableListOf()
+        val row = TileRow(columns = gridSizeInteractor.columns.value)
+        val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
+        val tilesQueue =
+            ArrayDeque(
+                tiles.map {
+                    SizedTile(
+                        it,
+                        width =
+                            if (iconTilesSet.contains(it)) {
+                                1
+                            } else {
+                                2
+                            }
+                    )
+                }
+            )
+
+        while (tilesQueue.isNotEmpty()) {
+            if (row.isFull()) {
+                newTiles.addAll(row.tileSpecs())
+                row.clear()
+            }
+
+            val tile = tilesQueue.removeFirst()
+
+            // If the tile fits in the row, add it.
+            if (!row.maybeAddTile(tile)) {
+                // If the tile does not fit the row, find an icon tile to move.
+                // We'll try to either add an icon tile from the queue to complete the row, or
+                // remove an icon tile from the current row to free up space.
+
+                val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 }
+                if (iconTile != null) {
+                    tilesQueue.remove(iconTile)
+                    tilesQueue.addFirst(tile)
+                    row.maybeAddTile(iconTile)
+                } else {
+                    val tileToRemove: SizedTile? = row.findLastIconTile()
+                    if (tileToRemove != null) {
+                        row.removeTile(tileToRemove)
+                        row.maybeAddTile(tile)
+
+                        // Moving the icon tile to the end because there's no other
+                        // icon tiles in the queue.
+                        tilesQueue.addLast(tileToRemove)
+                    } else {
+                        // If the row does not have an icon tile, add the incomplete row.
+                        // Note: this shouldn't happen because an icon tile is guaranteed to be in a
+                        // row that doesn't have enough space for a large tile.
+                        val tileSpecs = row.tileSpecs()
+                        Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
+                        newTiles.addAll(tileSpecs)
+                        row.clear()
+                        tilesQueue.addFirst(tile)
+                    }
+                }
+            }
+        }
+
+        // Add last row that might be incomplete
+        newTiles.addAll(row.tileSpecs())
+
+        return newTiles.toList()
+    }
+
+    /** Tile with a width representing the number of columns it should take. */
+    private data class SizedTile(val spec: TileSpec, val width: Int)
+
+    private class TileRow(private val columns: Int) {
+        private var availableColumns = columns
+        private val tiles: MutableList<SizedTile> = mutableListOf()
+
+        fun tileSpecs(): List<TileSpec> {
+            return tiles.map { it.spec }
+        }
+
+        fun maybeAddTile(tile: SizedTile): Boolean {
+            if (availableColumns - tile.width >= 0) {
+                tiles.add(tile)
+                availableColumns -= tile.width
+                return true
+            }
+            return false
+        }
+
+        fun findLastIconTile(): SizedTile? {
+            return tiles.findLast { it.width == 1 }
+        }
+
+        fun removeTile(tile: SizedTile) {
+            tiles.remove(tile)
+            availableColumns += tile.width
+        }
+
+        fun clear() {
+            tiles.clear()
+            availableColumns = columns
+        }
+
+        fun isFull(): Boolean = availableColumns == 0
+    }
+
+    private companion object {
+        const val TAG = "InfiniteGridConsistencyInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
index bf22563..13c6072 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+@SysUISingleton
+class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) {
+    val columns: StateFlow<Int> = repo.columns
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
new file mode 100644
index 0000000..0386a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */
+@SysUISingleton
+class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+    override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
index bf22563..884cde3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.shared.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import javax.inject.Qualifier
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class GridConsistencyLog()
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..dc43091 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
@@ -64,20 +66,20 @@
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.integerResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.onClick
 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
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
 import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
 import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
@@ -95,8 +97,12 @@
 import kotlinx.coroutines.flow.mapLatest
 
 @SysUISingleton
-class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) :
-    GridLayout {
+class InfiniteGridLayout
+@Inject
+constructor(
+    private val iconTilesInteractor: IconTilesInteractor,
+    private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridLayout {
 
     private object TileType
 
@@ -110,10 +116,10 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val iconTilesSpecs by
-            iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
+        val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState()
+        val columns by gridSizeInteractor.columns.collectAsState()
 
-        TileLazyGrid(modifier) {
+        TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
             items(
                 tiles.size,
                 span = { index ->
@@ -134,7 +140,7 @@
         }
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
+    @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
     @Composable
     private fun Tile(
         tile: TileViewModel,
@@ -147,28 +153,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
+                )
+            }
         }
     }
 
@@ -187,8 +204,9 @@
         val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
         val isIconOnly: (TileSpec) -> Boolean =
             remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+        val columns by gridSizeInteractor.columns.collectAsState()
 
-        TileLazyGrid(modifier = modifier) {
+        TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
             // These Text are just placeholders to see the different sections. Not final UI.
             item(span = { GridItemSpan(maxLineSpan) }) {
                 Text("Current tiles", color = Color.White)
@@ -372,11 +390,11 @@
 @Composable
 private fun TileLazyGrid(
     modifier: Modifier = Modifier,
+    columns: GridCells,
     content: LazyGridScope.() -> Unit,
 ) {
     LazyVerticalGrid(
-        columns =
-            GridCells.Fixed(integerResource(R.integer.quick_settings_infinite_grid_num_columns)),
+        columns = columns,
         verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
         horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
         modifier = modifier,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index af1d195..c8fbeb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.NewQsUI
+import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -34,6 +36,7 @@
     private val autoAddInteractor: AutoAddInteractor,
     private val featureFlags: QSPipelineFlagsRepository,
     private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
+    private val gridConsistencyInteractor: GridConsistencyInteractor,
 ) : CoreStartable {
 
     override fun start() {
@@ -41,6 +44,10 @@
             accessibilityTilesInteractor.init(currentTilesInteractor)
             autoAddInteractor.init(currentTilesInteractor)
             restoreReconciliationInteractor.start()
+
+            if (NewQsUI.isEnabled) {
+                gridConsistencyInteractor.start()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index ffa3b54..4c21080 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -25,10 +25,13 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 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.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -47,7 +50,7 @@
      * binding them together. This achieves a DI scope that lives along the instance of
      * [QSTileViewModelImpl].
      */
-    class Component<T>
+    class Component
     @Inject
     constructor(
         private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
@@ -58,7 +61,8 @@
         private val qsTileConfigProvider: QSTileConfigProvider,
         private val systemClock: SystemClock,
         @Background private val backgroundDispatcher: CoroutineDispatcher,
-    ) : QSTileViewModelFactory<T> {
+        private val customTileComponentBuilder: CustomTileComponent.Builder,
+    ) : QSTileViewModelFactory<CustomTileDataModel> {
 
         /**
          * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
@@ -66,10 +70,10 @@
          */
         fun create(
             tileSpec: TileSpec,
-            componentFactory: (config: QSTileConfig) -> QSTileComponent<T>
-        ): QSTileViewModelImpl<T> {
+        ): QSTileViewModel {
             val config = qsTileConfigProvider.getConfig(tileSpec.spec)
-            val component = componentFactory(config)
+            val component =
+                customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
             return QSTileViewModelImpl(
                 config,
                 component::userActionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 5122e1f..f65fdb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -22,9 +22,6 @@
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
-import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
-import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
@@ -41,8 +38,7 @@
     private val adapterFactory: QSTileViewModelAdapter.Factory,
     private val tileMap:
         Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
-    private val customTileComponentBuilder: CustomTileComponent.Builder,
-    private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>,
+    private val customTileViewModelFactory: QSTileViewModelFactory.Component,
 ) : QSFactory {
 
     init {
@@ -68,7 +64,5 @@
     }
 
     private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel =
-        customTileViewModelFactory.create(spec) { config ->
-            customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
-        }
+        customTileViewModelFactory.create(spec)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 17698f9d..6cf2e52 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -63,7 +63,7 @@
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     sceneBackInteractor: SceneBackInteractor,
-    val mediaDataManager: MediaDataManager,
+    val mediaCarouselInteractor: MediaCarouselInteractor,
 ) {
     private val backScene: StateFlow<SceneKey> =
         sceneBackInteractor.backScene
@@ -101,6 +101,8 @@
                     ),
             )
 
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
+
     private fun destinationScenes(
         isUnlocked: Boolean,
         canSwipeToDismiss: Boolean?,
@@ -143,9 +145,4 @@
         }
         return footerActionsViewModelFactory.create(lifecycleOwner)
     }
-
-    fun isMediaVisible(): Boolean {
-        // TODO(b/328207006): use new pipeline to handle updates while visible
-        return mediaDataManager.hasAnyMediaOrRecommendation()
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index d15a488..f509ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -61,7 +61,7 @@
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
     val brightnessMirrorViewModel: BrightnessMirrorViewModel,
-    val mediaDataManager: MediaDataManager,
+    val mediaCarouselInteractor: MediaCarouselInteractor,
     shadeInteractor: ShadeInteractor,
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
@@ -109,6 +109,8 @@
 
     val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
 
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+
     /**
      * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
      * slightly, in pixels.
@@ -126,11 +128,6 @@
         sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked")
     }
 
-    fun isMediaVisible(): Boolean {
-        // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
-        return mediaDataManager.hasActiveMediaOrRecommendation()
-    }
-
     private val footerActionsControllerInitialized = AtomicBoolean(false)
 
     fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
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/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/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/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
index ab6a37b..d9e19d8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -17,23 +17,15 @@
 package com.android.systemui.util.kotlin
 
 import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.mapNotNull
 
 object SharedPreferencesExt {
-    /**
-     * Returns a flow of [Unit] that is invoked each time shared preference is updated.
-     *
-     * @param key Optional key to limit updates to a particular key.
-     */
-    fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
-        conflatedCallbackFlow {
-                val listener =
-                    SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
-                registerOnSharedPreferenceChangeListener(listener)
-                awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
-            }
-            .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+    /** Returns a flow of [Unit] that is invoked each time shared preference is updated. */
+    fun SharedPreferences.observe(): Flow<Unit> = conflatedCallbackFlow {
+        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+        registerOnSharedPreferenceChangeListener(listener)
+        awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+    }
 }
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/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..db752dd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class GridConsistencyInteractorTest : SysuiTestCase() {
+
+    data object TestGridLayoutType : GridLayoutType
+
+    private val gridLayout: MutableStateFlow<GridLayoutType> =
+        MutableStateFlow(InfiniteGridLayoutType)
+
+    private val iconOnlyTiles =
+        MutableStateFlow(
+            setOf(
+                TileSpec.create("smallA"),
+                TileSpec.create("smallB"),
+                TileSpec.create("smallC"),
+                TileSpec.create("smallD"),
+                TileSpec.create("smallE"),
+            )
+        )
+
+    private val kosmos =
+        testKosmos().apply {
+            iconTilesRepository =
+                object : IconTilesRepository {
+                    override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+                        get() = iconOnlyTiles.asStateFlow()
+                }
+            gridConsistencyInteractorsMap =
+                mapOf(
+                    Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
+                    Pair(TestGridLayoutType, noopGridConsistencyInteractor)
+                )
+            gridLayoutTypeRepository =
+                object : GridLayoutTypeRepository {
+                    override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow()
+                }
+        }
+
+    private val underTest = with(kosmos) { gridConsistencyInteractor }
+
+    @Before
+    fun setUp() {
+        gridLayout.value = InfiniteGridLayoutType
+        underTest.start()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
+        with(kosmos) {
+            testScope.runTest {
+                // Using the no-op grid consistency interactor
+                gridLayout.value = TestGridLayoutType
+
+                // Setting an invalid layout with holes
+                // [ Large A ] [ sa ]
+                // [ Large B ] [ Large C ]
+                // [ sb ] [ Large D ]
+                val newTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeD"),
+                    )
+                tileSpecRepository.setTiles(0, newTiles)
+
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+                val tileSpecs = tiles.map { it.spec }
+
+                // Saved tiles should be unchanged
+                assertThat(tileSpecs).isEqualTo(newTiles)
+            }
+        }
+
+    @Test
+    fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
+        with(kosmos) {
+            testScope.runTest {
+                // Setting a valid layout with holes
+                // [ Large A ] [ sa ][ sb ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val newTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+                tileSpecRepository.setTiles(0, newTiles)
+
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+                val tileSpecs = tiles.map { it.spec }
+
+                // Saved tiles should be unchanged
+                assertThat(tileSpecs).isEqualTo(newTiles)
+            }
+        }
+
+    @Test
+    fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
+        with(kosmos) {
+            testScope.runTest {
+                // Setting an invalid layout with holes
+                // [ sa ] [ Large A ]
+                // [ Large B ] [ sb ] [ sc ]
+                // [ sd ] [ se ] [ Large C ]
+                val newTiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+                tileSpecRepository.setTiles(0, newTiles)
+
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+                val tileSpecs = tiles.map { it.spec }
+
+                // Expected grid
+                // [ sa ] [ Large A ] [ sb ]
+                // [ Large B ] [ sc ] [ sd ]
+                // [ se ] [ Large C ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+
+                // Saved tiles should be unchanged
+                assertThat(tileSpecs).isEqualTo(expectedTiles)
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..bda48ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
+
+    private val iconOnlyTiles =
+        MutableStateFlow(
+            setOf(
+                TileSpec.create("smallA"),
+                TileSpec.create("smallB"),
+                TileSpec.create("smallC"),
+                TileSpec.create("smallD"),
+                TileSpec.create("smallE"),
+            )
+        )
+    private val kosmos =
+        testKosmos().apply {
+            iconTilesRepository =
+                object : IconTilesRepository {
+                    override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+                        get() = iconOnlyTiles.asStateFlow()
+                }
+        }
+    private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
+
+    @Test
+    fun validTiles_returnsUnchangedList() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ Large A ] [ sa ][ sb ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(tiles)
+            }
+        }
+
+    @Test
+    fun invalidTiles_moveIconTileForward() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ Large A ] [ sa ]
+                // [ Large B ] [ Large C ]
+                // [ sb ] [ Large D ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeD"),
+                    )
+                // Expected grid
+                // [ Large A ] [ sa ][ sb ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(expectedTiles)
+            }
+        }
+
+    @Test
+    fun invalidTiles_moveIconTileBack() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ sa ] [ Large A ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+                // Expected grid
+                // [ Large A ] [ Large B ]
+                // [ Large C ] [ Large D ]
+                // [ sa ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                        TileSpec.create("smallA"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(expectedTiles)
+            }
+        }
+
+    @Test
+    fun invalidTiles_multipleCorrections() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ sa ] [ Large A ]
+                // [ Large B ] [ sb ] [ sc ]
+                // [ sd ] [ se ] [ Large C ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+                // Expected grid
+                // [ sa ] [ Large A ] [ sb ]
+                // [ Large B ] [ sc ] [ sd ]
+                // [ se ] [ Large C ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(expectedTiles)
+            }
+        }
+}
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/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 3f91122..d72630d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
 import com.android.systemui.qs.footer.foregroundServicesRepository
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -48,7 +49,7 @@
     QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
 }
 
-var Kosmos.qsTileFactory by Fixture<QSFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) }
 
 val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
 
@@ -98,3 +99,7 @@
         showPowerButton = true,
     )
 }
+
+private fun tileCreator(spec: String): QSTile {
+    return FakeQSTile(0).apply { tileSpec = spec }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
index f846d57..4acedaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() }
+var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by
+    Kosmos.Fixture { GridLayoutTypeRepositoryImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
index 685e772..e40152a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
@@ -18,4 +18,4 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
index bf22563..d8af3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.data.repository
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
new file mode 100644
index 0000000..edbc4c1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.gridConsistencyInteractor by
+    Kosmos.Fixture {
+        GridConsistencyInteractor(
+            gridLayoutTypeInteractor,
+            currentTilesInteractor,
+            gridConsistencyInteractorsMap,
+            noopGridConsistencyInteractor,
+            FakeLogBuffer.Factory.create(),
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index c951642..34e99d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -27,3 +27,6 @@
 
 val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
     Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+
+var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
+    Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
index bf22563..7f387d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridConsistencyInteractor by
+    Kosmos.Fixture {
+        InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 1893c30..34b266a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -19,4 +19,5 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 
-val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) }
+val Kosmos.infiniteGridLayout by
+    Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
index bf22563..6e11977 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridSizeInteractor by
+    Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
index bf22563..e3beff7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 5fd8762..9481fca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,8 +20,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
 val Kosmos.tileGridViewModel by
@@ -30,7 +29,7 @@
             gridLayoutTypeInteractor,
             gridLayoutMap,
             currentTilesInteractor,
-            InfiniteGridLayout(iconTilesInteractor),
+            infiniteGridLayout,
             applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index 5c4b390..419e781 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -16,23 +16,63 @@
 
 package com.android.systemui.qs.tiles.di
 
+import android.os.UserHandle
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import javax.inject.Provider
-import org.mockito.Mockito
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
 
 var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
 
+val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by
+    Kosmos.Fixture {
+        mock {
+            whenever(create(any())).thenAnswer { invocation ->
+                val tileSpec = invocation.getArgument<TileSpec>(0)
+                val config =
+                    QSTileConfig(
+                        tileSpec,
+                        QSTileUIConfig.Empty,
+                        instanceIdSequenceFake.newInstanceId(),
+                    )
+                object : QSTileViewModel {
+                    override val state: SharedFlow<QSTileState> =
+                        MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+                    override val config: QSTileConfig = config
+                    override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+
+                    override fun onUserChanged(user: UserHandle) {}
+
+                    override fun forceUpdate() {}
+
+                    override fun onActionPerformed(userAction: QSTileUserAction) {}
+
+                    override fun destroy() {}
+                }
+            }
+        }
+    }
+
 val Kosmos.newQSTileFactory by
     Kosmos.Fixture {
         NewQSTileFactory(
             qSTileConfigProvider,
             qsTileViewModelAdaperFactory,
             newFactoryTileMap,
-            mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
-            mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+            customTileViewModelFactory,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 561e254..7b9992d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -38,10 +38,10 @@
 import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
 
 var Kosmos.customTileQsTileConfig: QSTileConfig by
-    Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+    Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.customTileSpec } }
 val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
 
 val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
@@ -50,7 +50,7 @@
 val Kosmos.customTileInteractor: CustomTileInteractor by
     Kosmos.Fixture {
         CustomTileInteractor(
-            tileSpec,
+            customTileSpec,
             customTileDefaultsRepository,
             customTileRepository,
             testScope.backgroundScope,
@@ -61,7 +61,7 @@
 val Kosmos.customTileRepository: FakeCustomTileRepository by
     Kosmos.Fixture {
         FakeCustomTileRepository(
-            tileSpec,
+            customTileSpec,
             customTileStatePersister,
             packageManagerAdapterFacade,
             testScope.testScheduler,
@@ -75,12 +75,12 @@
     Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
 
 val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
-    Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+    Kosmos.Fixture { FakePackageManagerAdapterFacade(customTileSpec.componentName) }
 
 val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
     Kosmos.Fixture {
         CustomTileServiceInteractor(
-            tileSpec,
+            customTileSpec,
             activityStarter,
             { customTileUserActionInteractor },
             customTileInteractor,
@@ -95,7 +95,7 @@
     Kosmos.Fixture {
         CustomTileUserActionInteractor(
             testCase.context,
-            tileSpec,
+            customTileSpec,
             qsTileLogger,
             mock {},
             mock {},
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 249b3cb..06a0297 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5731,7 +5731,9 @@
                         // so we tear it down in anticipation of it (possibly) being
                         // reconstructed due to the restore
                         host.widgets.remove(widget);
-                        provider.widgets.remove(widget);
+                        if (provider != null) {
+                            provider.widgets.remove(widget);
+                        }
                         // Check if we need to destroy any services (if no other app widgets are
                         // referencing the same service)
                         decrementAppWidgetServiceRefCount(widget);
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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index add8491..c310822 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9778,9 +9778,9 @@
             mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                 Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this);
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.MASTER_MONO), false, this);
+                    Settings.System.MASTER_MONO), false, this, UserHandle.USER_ALL);
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.MASTER_BALANCE), false, this);
+                    Settings.System.MASTER_BALANCE), false, this, UserHandle.USER_ALL);
 
             mEncodedSurroundMode = mSettings.getGlobalInt(
                     mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,
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/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/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/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 93f26ae..c85ceac 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -642,6 +642,8 @@
                         .getPackages()
                         .get(0)
                         .getVersionRolledBackFrom();
+        Slog.i(TAG, "Rolling back high impact rollback for package: "
+                + firstRollback.getPackageName());
         rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
     }
 
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 519c0ed..7fc0292 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -293,6 +293,8 @@
                 return "REASON_APP_NOT_RESPONDING";
             case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
                 return "REASON_NATIVE_CRASH_DURING_BOOT";
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
+                return "REASON_BOOT_LOOP";
             default:
                 return "UNKNOWN";
         }
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index 2bf0b2c..55f85ea2 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -22,6 +22,8 @@
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -31,12 +33,20 @@
 import android.security.attestationverification.IAttestationVerificationManagerService;
 import android.security.attestationverification.IVerificationResult;
 import android.security.attestationverification.VerificationToken;
+import android.text.TextUtils;
 import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.TimeUtils;
 
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+
 /**
  * A {@link SystemService} which provides functionality related to verifying attestations of
  * (usually) remote computing environments.
@@ -46,11 +56,13 @@
 public class AttestationVerificationManagerService extends SystemService {
 
     private static final String TAG = "AVF";
+    private static final int DUMP_EVENT_LOG_SIZE = 10;
     private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier;
+    private final DumpLogger mDumpLogger = new DumpLogger();
 
     public AttestationVerificationManagerService(final Context context) throws Exception {
         super(context);
-        mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context);
+        mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger);
     }
 
     private final IBinder mService = new IAttestationVerificationManagerService.Stub() {
@@ -83,6 +95,28 @@
         private void enforceUsePermission() {
             getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null);
         }
+
+        @Override
+        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+                @Nullable String[] args) {
+            if (!android.security.Flags.dumpAttestationVerifications()) {
+                super.dump(fd, writer, args);
+                return;
+            }
+
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return;
+
+            final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "    ");
+
+            fout.print("AttestationVerificationManagerService");
+            fout.println();
+            fout.increaseIndent();
+
+            fout.println("Event Log:");
+            fout.increaseIndent();
+            mDumpLogger.dumpTo(fout);
+            fout.decreaseIndent();
+        }
     };
 
     private void verifyAttestationForAllVerifiers(
@@ -119,4 +153,45 @@
         Slog.d(TAG, "Started");
         publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService);
     }
+
+
+    static class DumpLogger {
+        private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE);
+        private int mEventsLogged = 0;
+
+        void logAttempt(DumpData data) {
+            synchronized (mData) {
+                if (mData.size() == DUMP_EVENT_LOG_SIZE) {
+                    mData.removeFirst();
+                }
+
+                mEventsLogged++;
+                data.mEventNumber = mEventsLogged;
+
+                data.mEventTimeMs = System.currentTimeMillis();
+
+                mData.add(data);
+            }
+        }
+
+        void dumpTo(IndentingPrintWriter writer) {
+            synchronized (mData) {
+                for (DumpData data : mData.reversed()) {
+                    writer.println(
+                            TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber,
+                                    TimeUtils.formatForLogging(data.mEventTimeMs)));
+                    writer.increaseIndent();
+                    data.dumpTo(writer);
+                    writer.decreaseIndent();
+                }
+            }
+        }
+    }
+
+    abstract static class DumpData {
+        protected int mEventNumber = -1;
+        protected long mEventTimeMs = -1;
+
+        abstract void dumpTo(IndentingPrintWriter writer);
+    }
 }
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index 72a402d..945a340 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -30,15 +30,19 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
+import android.security.attestationverification.AttestationVerificationManager;
 import android.security.attestationverification.AttestationVerificationManager.LocalBindingType;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
 
 import org.json.JSONObject;
 
@@ -71,7 +75,9 @@
 
 /**
  * Verifies Android key attestation according to the
- * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE}
+ * {@link
+ * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE
+ * PROFILE_PEER_DEVICE}
  * profile.
  *
  * <p>
@@ -118,9 +124,12 @@
     private final LocalDate mTestLocalPatchDate;
     private final CertificateFactory mCertificateFactory;
     private final CertPathValidator mCertPathValidator;
+    private final DumpLogger mDumpLogger;
 
-    AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception {
+    AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
+            @NonNull DumpLogger dumpLogger) throws Exception {
         mContext = Objects.requireNonNull(context);
+        mDumpLogger = dumpLogger;
         mCertificateFactory = CertificateFactory.getInstance("X.509");
         mCertPathValidator = CertPathValidator.getInstance("PKIX");
         mTrustAnchors = getTrustAnchors();
@@ -132,9 +141,10 @@
     // Use ONLY for hermetic unit testing.
     @VisibleForTesting
     AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
-            Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
+            DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
             LocalDate systemDate, LocalDate localPatchDate) throws Exception {
         mContext = Objects.requireNonNull(context);
+        mDumpLogger = dumpLogger;
         mCertificateFactory = CertificateFactory.getInstance("X.509");
         mCertPathValidator = CertPathValidator.getInstance("PKIX");
         mTrustAnchors = trustAnchors;
@@ -153,63 +163,90 @@
      * bounded at the end by {@code -----END CERTIFICATE-----}.
      *
      * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported.
-     * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
-     * @param attestation Certificates should be DER encoded with leaf certificate appended first.
+     * @param requirements     Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
+     * @param attestation      Certificates should be DER encoded with leaf certificate appended
+     *                         first.
      */
     int verifyAttestation(
             @LocalBindingType int localBindingType,
             @NonNull Bundle requirements,
             @NonNull byte[] attestation) {
+
+        MyDumpData dumpData = new MyDumpData();
+
+        int result =
+                verifyAttestationInternal(localBindingType, requirements, attestation, dumpData);
+        dumpData.mResult = result;
+        mDumpLogger.logAttempt(dumpData);
+        return result;
+    }
+
+    private int verifyAttestationInternal(
+            @LocalBindingType int localBindingType,
+            @NonNull Bundle requirements,
+            @NonNull byte[] attestation,
+            @NonNull MyDumpData dumpData) {
         if (mCertificateFactory == null) {
             debugVerboseLog("Unable to access CertificateFactory");
             return RESULT_FAILURE;
         }
+        dumpData.mCertificationFactoryAvailable = true;
 
         if (mCertPathValidator == null) {
             debugVerboseLog("Unable to access CertPathValidator");
             return RESULT_FAILURE;
         }
+        dumpData.mCertPathValidatorAvailable = true;
+
 
         // Check if the provided local binding type is supported and if the provided requirements
         // "match" the binding type.
         if (!validateAttestationParameters(localBindingType, requirements)) {
             return RESULT_FAILURE;
         }
+        dumpData.mAttestationParametersOk = true;
+
+        // To provide the most information in the dump logs, we track the failure state but keep
+        // verifying the rest of the attestation. For code safety, there are no transitions past
+        // here to set failed = false
+        boolean failed = false;
 
         try {
             // First: parse and validate the certificate chain.
             final List<X509Certificate> certificateChain = getCertificates(attestation);
             // (returns void, but throws CertificateException and other similar Exceptions)
             validateCertificateChain(certificateChain);
+            dumpData.mCertChainOk = true;
 
             final var leafCertificate = certificateChain.get(0);
             final var attestationExtension = fromCertificate(leafCertificate);
 
             // Second: verify if the attestation satisfies the "peer device" profile.
-            if (!checkAttestationForPeerDeviceProfile(attestationExtension)) {
-                return RESULT_FAILURE;
+            if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) {
+                failed = true;
             }
 
             // Third: check if the attestation satisfies local binding requirements.
             if (!checkLocalBindingRequirements(
-                    leafCertificate, attestationExtension, localBindingType, requirements)) {
-                return RESULT_FAILURE;
+                    leafCertificate, attestationExtension, localBindingType, requirements,
+                    dumpData)) {
+                failed = true;
             }
-
-            return RESULT_SUCCESS;
         } catch (CertificateException | CertPathValidatorException
-                | InvalidAlgorithmParameterException | IOException e) {
+                 | InvalidAlgorithmParameterException | IOException e) {
             // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates()
             // or validateCertificateChain() or
             // AndroidKeystoreAttestationVerificationAttributes.fromCertificate())
             debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e);
-            return RESULT_FAILURE;
+            failed = true;
         } catch (RuntimeException e) {
             // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions
             // out of this class/method.
             debugVerboseLog("Unexpected error", e);
-            return RESULT_FAILURE;
+            failed = true;
         }
+
+        return failed ? RESULT_FAILURE : RESULT_SUCCESS;
     }
 
     @NonNull
@@ -255,7 +292,7 @@
 
     private void validateCertificateChain(List<X509Certificate> certificates)
             throws CertificateException, CertPathValidatorException,
-            InvalidAlgorithmParameterException  {
+            InvalidAlgorithmParameterException {
         if (certificates.size() < 2) {
             debugVerboseLog("Certificate chain less than 2 in size.");
             throw new CertificateException("Certificate chain less than 2 in size.");
@@ -277,7 +314,7 @@
     private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
         Set<TrustAnchor> modifiableSet = new HashSet<>();
         try {
-            for (String certString: getTrustAnchorResources()) {
+            for (String certString : getTrustAnchorResources()) {
                 modifiableSet.add(
                         new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate(
                                 new ByteArrayInputStream(getCertificateBytes(certString))), null));
@@ -307,8 +344,9 @@
             @NonNull X509Certificate leafCertificate,
             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
             @LocalBindingType int localBindingType,
-            @NonNull Bundle requirements) {
+            @NonNull Bundle requirements, MyDumpData dumpData) {
         // First: check non-optional (for the given local binding type) requirements.
+        dumpData.mBindingType = localBindingType;
         switch (localBindingType) {
             case TYPE_PUBLIC_KEY:
                 // Verify leaf public key matches provided public key.
@@ -336,9 +374,11 @@
                 throw new IllegalArgumentException("Unsupported local binding type "
                         + localBindingTypeToString(localBindingType));
         }
+        dumpData.mBindingOk = true;
 
         // Second: check specified optional requirements.
         if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) {
+            dumpData.mSystemOwnershipChecked = true;
             if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) {
                 // Verify key is owned by the system.
                 final boolean ownedBySystem = checkOwnedBySystem(
@@ -347,6 +387,7 @@
                     debugVerboseLog("Certificate public key is not owned by the AndroidSystem.");
                     return false;
                 }
+                dumpData.mSystemOwned = true;
             } else {
                 throw new IllegalArgumentException("The value of the requirement key "
                         + PARAM_OWNED_BY_SYSTEM
@@ -359,73 +400,98 @@
     }
 
     private boolean checkAttestationForPeerDeviceProfile(
-            @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) {
+            @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
+            MyDumpData dumpData) {
+        boolean result = true;
+
         // Checks for support of Keymaster 4.
         if (attestationAttributes.getAttestationVersion() < 3) {
             debugVerboseLog("Attestation version is not at least 3 (Keymaster 4).");
-            return false;
+            result = false;
+        } else {
+            dumpData.mAttestationVersionAtLeast3 = true;
         }
 
         // Checks for support of Keymaster 4.
         if (attestationAttributes.getKeymasterVersion() < 4) {
             debugVerboseLog("Keymaster version is not at least 4.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeymasterVersionAtLeast4 = true;
         }
 
         // First two characters are Android OS version.
         if (attestationAttributes.getKeyOsVersion() < 100000) {
             debugVerboseLog("Android OS version is not 10+.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mOsVersionAtLeast10 = true;
         }
 
         if (!attestationAttributes.isAttestationHardwareBacked()) {
             debugVerboseLog("Key is not HW backed.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyHwBacked = true;
         }
 
         if (!attestationAttributes.isKeymasterHardwareBacked()) {
             debugVerboseLog("Keymaster is not HW backed.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeymasterHwBacked = true;
         }
 
         if (attestationAttributes.getVerifiedBootState() != VERIFIED) {
             debugVerboseLog("Boot state not Verified.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mBootStateIsVerified = true;
         }
 
         try {
             if (!attestationAttributes.isVerifiedBootLocked()) {
                 debugVerboseLog("Verified boot state is not locked.");
-                return false;
+                result = false;
+            } else {
+                dumpData.mVerifiedBootStateLocked = true;
             }
         } catch (IllegalStateException e) {
             debugVerboseLog("VerifiedBootLocked is not set.", e);
-            return false;
+            result = false;
         }
 
         // Patch level integer YYYYMM is expected to be within 1 year of today.
         if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) {
             debugVerboseLog("OS patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mOsPatchLevelInRange = true;
         }
 
         // Patch level integer YYYYMMDD is expected to be within 1 year of today.
         if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
             debugVerboseLog("Boot patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyBootPatchLevelInRange = true;
         }
 
         if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) {
             debugVerboseLog("Vendor patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyVendorPatchLevelInRange = true;
         }
 
         if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
             debugVerboseLog("Boot patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyBootPatchLevelInRange = true;
         }
 
-        return true;
+        return result;
     }
 
     private boolean checkPublicKey(
@@ -609,4 +675,99 @@
             Slog.v(TAG, str);
         }
     }
+
+    /* Mutable data class for tracking dump data from verifications. */
+    private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
+
+        // Top-Level Result
+        int mResult = -1;
+
+        // Configuration/Setup preconditions
+        boolean mCertificationFactoryAvailable = false;
+        boolean mCertPathValidatorAvailable = false;
+
+        // AttestationParameters (Valid Input Only)
+        boolean mAttestationParametersOk = false;
+
+        // Certificate Chain (Structure & Chaining Conditions)
+        boolean mCertChainOk = false;
+
+        // Binding
+        boolean mBindingOk = false;
+        int mBindingType = -1;
+
+        // System Ownership
+        boolean mSystemOwnershipChecked = false;
+        boolean mSystemOwned = false;
+
+        // Android Keystore attestation properties
+        boolean mOsVersionAtLeast10 = false;
+        boolean mKeyHwBacked = false;
+        boolean mAttestationVersionAtLeast3 = false;
+        boolean mKeymasterVersionAtLeast4 = false;
+        boolean mKeymasterHwBacked = false;
+        boolean mBootStateIsVerified = false;
+        boolean mVerifiedBootStateLocked = false;
+        boolean mOsPatchLevelInRange = false;
+        boolean mKeyBootPatchLevelInRange = false;
+        boolean mKeyVendorPatchLevelInRange = false;
+
+        @SuppressLint("WrongConstant")
+        @Override
+        public void dumpTo(IndentingPrintWriter writer) {
+            writer.println(
+                    "Result: " + AttestationVerificationManager.verificationResultCodeToString(
+                            mResult));
+            if (!mCertificationFactoryAvailable) {
+                writer.println("Certificate Factory Unavailable");
+                return;
+            }
+            if (!mCertPathValidatorAvailable) {
+                writer.println("Cert Path Validator Unavailable");
+                return;
+            }
+            if (!mAttestationParametersOk) {
+                writer.println("Attestation parameters set incorrectly.");
+                return;
+            }
+
+            writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail(
+                    mCertChainOk));
+            if (!mCertChainOk) {
+                return;
+            }
+
+            // Binding
+            writer.println("Local Binding: " + booleanToOkFail(mBindingOk));
+            writer.increaseIndent();
+            writer.println("Binding Type: " + mBindingType);
+            writer.decreaseIndent();
+
+            if (mSystemOwnershipChecked) {
+                writer.println("System Ownership: " + booleanToOkFail(mSystemOwned));
+            }
+
+            // Keystore Attestation params
+            writer.println("KeyStore Attestation Parameters");
+            writer.increaseIndent();
+            writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10));
+            writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange));
+            writer.println(
+                    "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3));
+            writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4));
+            writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked));
+            writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked));
+            writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified));
+            writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked));
+            writer.println(
+                    "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange));
+            writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail(
+                    mKeyVendorPatchLevelInRange));
+            writer.decreaseIndent();
+        }
+
+        private String booleanToOkFail(boolean value) {
+            return value ? "OK" : "FAILURE";
+        }
+    }
 }
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/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index dddc7b1..8fb83fa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1447,14 +1447,17 @@
                     + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
         }
 
+        final boolean contentChanged = didFrameInsetsChange || configChanged
+                || dragResizingChanged || attachedFrameChanged;
+        // Cancel unchanged non-sync-buffer redraw request to avoid unnecessary reportResized().
+        if (!contentChanged && !mRedrawForSyncReported && mPrepareSyncSeqId <= 0
+                && mDrawHandlers.isEmpty()) {
+            mRedrawForSyncReported = true;
+        }
+
         // Add a window that is using blastSync to the resizing list if it hasn't been reported
         // already. This because the window is waiting on a finishDrawing from the client.
-        if (didFrameInsetsChange
-                || configChanged
-                || insetsChanged
-                || dragResizingChanged
-                || shouldSendRedrawForSync()
-                || attachedFrameChanged) {
+        if (contentChanged || insetsChanged || shouldSendRedrawForSync()) {
             ProtoLog.v(WM_DEBUG_RESIZE,
                         "Resize reasons for w=%s:  %s configChanged=%b didFrameInsetsChange=%b",
                         this, mWindowFrames.getInsetsChangedInfo(),
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 ba89fda..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;
@@ -624,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,
@@ -2946,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/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/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/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/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/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fbbb9a2..b152c3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -819,17 +819,20 @@
         assertFalse(win.getOrientationChanging());
     }
 
-    @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
     @Test
     public void testRequestResizeForBlastSync() {
-        final WindowState win = mChildAppWindowAbove;
-        makeWindowVisible(win, win.getParentWindow());
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "window");
+        makeWindowVisible(win);
+        makeLastConfigReportedToClient(win, true /* visible */);
         win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
         win.reportResized();
         win.updateResizingWindowIfNeeded();
         assertThat(mWm.mResizingWindows).doesNotContain(win);
 
         // Check that the window is in resizing if using blast sync.
+        final BLASTSyncEngine.SyncGroup syncGroup = mock(BLASTSyncEngine.SyncGroup.class);
+        syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_BLAST;
+        win.mSyncGroup = syncGroup;
         win.reportResized();
         win.prepareSync();
         assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
@@ -842,6 +845,20 @@
         mWm.mResizingWindows.remove(win);
         win.updateResizingWindowIfNeeded();
         assertThat(mWm.mResizingWindows).doesNotContain(win);
+
+        // Non blast sync doesn't require to force resizing, because it won't use syncSeqId.
+        // And if the window is already drawn, it can report sync finish immediately so that the
+        // sync group won't be blocked.
+        win.finishSync(mTransaction, syncGroup, false /* cancel */);
+        syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_NONE;
+        win.mSyncGroup = syncGroup;
+        win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        win.prepareSync();
+        assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
+        win.updateResizingWindowIfNeeded();
+        assertThat(mWm.mResizingWindows).doesNotContain(win);
+        assertTrue(win.isSyncFinished(syncGroup));
+        assertEquals(WindowContainer.SYNC_STATE_READY, win.mSyncState);
     }
 
     @Test
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
index dfbbda6c..afb3593 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -9,21 +9,28 @@
 import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
 import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
 import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.util.IndentingPrintWriter
+import android.util.Log
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
+import java.io.StringWriter
 import java.security.cert.Certificate
 import java.security.cert.CertificateFactory
 import java.security.cert.TrustAnchor
 import java.security.cert.X509Certificate
 import java.time.LocalDate
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
 
 /** Test for Peer Device attestation verifier. */
 @SmallTest
@@ -31,6 +38,7 @@
 class AttestationVerificationPeerDeviceVerifierTest {
     private val certificateFactory = CertificateFactory.getInstance("X.509")
     @Mock private lateinit var context: Context
+    private val dumpLogger = DumpLogger()
     private lateinit var trustAnchors: HashSet<TrustAnchor>
 
     @Before
@@ -44,37 +52,50 @@
         }
     }
 
+    @After
+    fun dumpAndLog() {
+        val dump = dumpLogger.getDump()
+        Log.d(TAG, "$dump")
+    }
+
     @Test
     fun verifyAttestation_returnsSuccessTypeChallenge() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-            LocalDate.of(2021, 8, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 8, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-            LocalDate.of(2021, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsSuccessTypePublicKey() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-            LocalDate.of(2021, 8, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 8, 1)
+        )
 
         val leafCert =
             (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0]
@@ -84,61 +105,75 @@
 
         val result = verifier.verifyAttestation(
             TYPE_PUBLIC_KEY, pkRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsSuccessOwnedBySystem() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-                context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-                LocalDate.of(2021, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray())
         challengeRequirements.putBoolean("android.key_owned_by_system", true)
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-                TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()
+        )
+
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsFailureOwnedBySystem() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-                context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-                LocalDate.of(2021, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
         challengeRequirements.putBoolean("android.key_owned_by_system", true)
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-                TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     @Test
     fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2023, 3, 1),
-            LocalDate.of(2023, 2, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1),
+            LocalDate.of(2023, 2, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     @Test
     fun verifyAttestation_returnsFailureTrustedAnchorEmpty() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, HashSet(), false, LocalDate.of(2022, 1, 1),
-            LocalDate.of(2022, 1, 1))
+            context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1),
+            LocalDate.of(2022, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
@@ -151,32 +186,39 @@
         }
 
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
-            LocalDate.of(2022, 1, 1))
+            context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
+            LocalDate.of(2022, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     fun verifyAttestation_returnsFailureChallenge() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 1, 1),
-            LocalDate.of(2022, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1),
+            LocalDate.of(2022, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     private fun String.fromPEMFileToCerts(): Collection<Certificate> {
         return certificateFactory.generateCertificates(
             InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets()
-                .open(this))
+                .open(this)
+        )
     }
 
     private fun String.fromPEMFileToByteArray(): ByteArray {
@@ -188,6 +230,12 @@
         return bos.toByteArray()
     }
 
+    private fun DumpLogger.getDump(): String {
+        val sw = StringWriter()
+        this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " "))
+        return sw.toString()
+    }
+
     class TestActivity : Activity() {
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
@@ -195,6 +243,7 @@
     }
 
     companion object {
+        private const val TAG = "AVFTest"
         private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem"
         private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME =
             "test_attestation_with_root_certs.pem"
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/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;
     }