Merge "Fix IndexOutOfBoundsException in AvalancheController.getDurationMs" into main
diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml
index c9d45a6..db938e4 100644
--- a/apct-tests/perftests/packagemanager/AndroidTest.xml
+++ b/apct-tests/perftests/packagemanager/AndroidTest.xml
@@ -76,11 +76,6 @@
         <option name="test-file-name" value="QueriesAll49.apk"/>
     </target_preparer>
 
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.perftests.packagemanager"/>
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="directory-keys" value="/data/local/PackageManagerPerfTests"/>
         <option name="collect-on-run-ended-only" value="true"/>
diff --git a/core/api/current.txt b/core/api/current.txt
index c189a24c..948eb68 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -282,7 +282,7 @@
     field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
-    field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
+    field @FlaggedApi("android.companion.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
     field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 443a6c0e..1383096 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1769,14 +1769,16 @@
   }
 
   public final class InputManager {
-    method 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 @FlaggedApi("com.android.input.flags.device_associations") @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 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 @FlaggedApi("com.android.input.flags.device_associations") @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 685ea63..e6265ae 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -977,8 +977,16 @@
     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#addUniqueIdAssociationByPort(String, String):
+    Method 'addUniqueIdAssociationByPort' 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#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():
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dbde7d2..6ff1bfc5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2583,6 +2583,7 @@
         this.when = System.currentTimeMillis();
         if (updateRankingTime()) {
             creationTime = when;
+            extras.putBoolean(EXTRA_SHOW_WHEN, true);
         } else {
             this.creationTime = System.currentTimeMillis();
         }
@@ -2598,6 +2599,7 @@
     {
         if (updateRankingTime()) {
             creationTime = when;
+            extras.putBoolean(EXTRA_SHOW_WHEN, true);
         }
         new Builder(context)
                 .setWhen(when)
@@ -2630,6 +2632,7 @@
         this.when = when;
         if (updateRankingTime()) {
             creationTime = when;
+            extras.putBoolean(EXTRA_SHOW_WHEN, true);
         } else {
             this.creationTime = System.currentTimeMillis();
         }
@@ -4382,14 +4385,16 @@
         /**
          * Add a timestamp pertaining to the notification (usually the time the event occurred).
          *
-         * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
-         * shown anymore by default and must be opted into by using
-         * {@link android.app.Notification.Builder#setShowWhen(boolean)}
-         *
          * @see Notification#when
          */
         @NonNull
         public Builder setWhen(long when) {
+            if (updateRankingTime()) {
+                // don't show a timestamp that's decades old
+                if (mN.extras.getBoolean(EXTRA_SHOW_WHEN, true) && when == 0) {
+                    return this;
+                }
+            }
             mN.when = when;
             return this;
         }
@@ -4397,8 +4402,6 @@
         /**
          * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
          * in the content view.
-         * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
-         * {@code false}. For earlier apps, the default is {@code true}.
          */
         @NonNull
         public Builder setShowWhen(boolean show) {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index eb7afb8e..481ff2e 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -134,6 +134,13 @@
             "enable_credential_description_api";
 
     /**
+     * @hide
+     */
+    @Hide
+    public static final String EXTRA_AUTOFILL_RESULT_RECEIVER =
+            "android.credentials.AUTOFILL_RESULT_RECEIVER";
+
+    /**
      * @hide instantiated by ContextImpl.
      */
     public CredentialManager(Context context, ICredentialManager service) {
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
index 2229f25..a620621 100644
--- a/core/java/android/credentials/selection/Constants.java
+++ b/core/java/android/credentials/selection/Constants.java
@@ -28,12 +28,5 @@
      */
     public static final String EXTRA_RESULT_RECEIVER =
             "android.credentials.selection.extra.RESULT_RECEIVER";
-
-    /**
-     * The intent extra key for the final result receiver object
-     */
-    public static final String EXTRA_FINAL_RESPONSE_RECEIVER =
-            "android.credentials.selection.extra.FINAL_RESPONSE_RECEIVER";
-
     private Constants() {}
 }
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index 32938ff..e5c12e3 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -16,9 +16,11 @@
 package android.hardware;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Range;
 
 import java.util.ArrayList;
 import java.util.List;
+
 /**
  * The camera action state used for passing camera usage information from
  * camera service to camera service proxy .
@@ -66,6 +68,7 @@
     private int mVideoStabilizationMode;
     private boolean mUsedUltraWide;
     private boolean mUsedZoomOverride;
+    private Range<Integer> mMostRequestedFpsRange;
     private int mSessionIndex;
     private CameraExtensionSessionStats mCameraExtensionSessionStats;
 
@@ -86,6 +89,7 @@
         mVideoStabilizationMode = -1;
         mUsedUltraWide = false;
         mUsedZoomOverride = false;
+        mMostRequestedFpsRange = new Range<Integer>(0, 0);
         mSessionIndex = 0;
         mCameraExtensionSessionStats = new CameraExtensionSessionStats();
     }
@@ -109,6 +113,7 @@
         mVideoStabilizationMode = -1;
         mUsedUltraWide = false;
         mUsedZoomOverride = false;
+        mMostRequestedFpsRange = new Range<Integer>(0, 0);
         mSessionIndex = sessionIdx;
         mCameraExtensionSessionStats = new CameraExtensionSessionStats();
     }
@@ -158,6 +163,8 @@
         dest.writeBoolean(mUsedZoomOverride);
         dest.writeInt(mSessionIndex);
         mCameraExtensionSessionStats.writeToParcel(dest, 0);
+        dest.writeInt(mMostRequestedFpsRange.getLower());
+        dest.writeInt(mMostRequestedFpsRange.getUpper());
     }
 
     public void readFromParcel(Parcel in) {
@@ -188,6 +195,9 @@
 
         mSessionIndex = in.readInt();
         mCameraExtensionSessionStats = CameraExtensionSessionStats.CREATOR.createFromParcel(in);
+        int minFps = in.readInt();
+        int maxFps = in.readInt();
+        mMostRequestedFpsRange = new Range<Integer>(minFps, maxFps);
     }
 
     public String getCameraId() {
@@ -273,4 +283,8 @@
     public CameraExtensionSessionStats getExtensionSessionStats() {
         return mCameraExtensionSessionStats;
     }
+
+    public Range<Integer> getMostRequestedFpsRange() {
+        return mMostRequestedFpsRange;
+    }
 }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 553d9f7..5a0f3db 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -164,15 +164,9 @@
     void getFeature(IBinder token, int userId, int feature, IFaceServiceReceiver receiver,
             String opPackageName);
 
-    // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
-    // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
-    // hidlSensors must be non-null and empty. See AuthService.java
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
-
     //Register all available face sensors.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+    void registerAuthenticators(in FaceSensorConfigurations faceSensorConfigurations);
 
     // Adds a callback which gets called when the service registers all of the face
     // authenticators. The callback is automatically removed after it's invoked.
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 8b37c24..6a96ac4 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -177,13 +177,7 @@
 
     //Register all available fingerprint sensors.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
-
-    // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
-    // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
-    // hidlSensors must be non-null and empty. See AuthService.java
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void registerAuthenticators(in List<FingerprintSensorPropertiesInternal> hidlSensors);
+    void registerAuthenticators(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
 
     // Adds a callback which gets called when the service registers all of the fingerprint
     // authenticators. The callback is automatically removed after it's invoked.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1c37aa2..2d474d6 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -165,10 +165,17 @@
     // static association for the cleared input port will be restored.
     void removePortAssociation(in String inputPort);
 
-    // Add a runtime association between the input device and display.
-    void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
-    // Remove the runtime association between the input device and display.
-    void removeUniqueIdAssociation(in String inputPort);
+    // Add a runtime association between the input device and display, using device's descriptor.
+    void addUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor,
+            in String displayUniqueId);
+    // Remove the runtime association between the input device and display, using device's
+    // descriptor.
+    void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor);
+
+    // Add a runtime association between the input device and display, using device's port.
+    void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId);
+    // Remove the runtime association between the input device and display, using device's port.
+    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 f949158..4dda2c7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
+import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
 import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
 
 import android.Manifest;
@@ -1054,13 +1055,14 @@
     /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
-     * @param inputPort The port of the input device.
-     * @param displayPort The physical port of the associated display.
+     * @param inputPort the port of the input device
+     * @param displayPort the physical port of the associated display
      * <p>
      * Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     public void addPortAssociation(@NonNull String inputPort, int displayPort) {
         try {
             mIm.addPortAssociation(inputPort, displayPort);
@@ -1072,12 +1074,13 @@
     /**
      * Remove the runtime association between the input port and the display port. Any existing
      * static association for the cleared input port will be restored.
-     * @param inputPort The port of the input device to be cleared.
+     * @param inputPort the port of the input device to be cleared
      * <p>
      * Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     public void removePortAssociation(@NonNull String inputPort) {
         try {
             mIm.removePortAssociation(inputPort);
@@ -1089,30 +1092,74 @@
     /**
      * Add a runtime association between the input port and display, by unique id. Input ports are
      * expected to be unique.
-     * @param inputPort The port of the input device.
-     * @param displayUniqueId The unique id of the associated display.
+     * @param inputPort the port of the input device
+     * @param displayUniqueId the unique id of the associated display
      * <p>
      * Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
+    @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+    @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     @TestApi
-    public void addUniqueIdAssociation(@NonNull String inputPort,
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
             @NonNull String displayUniqueId) {
-        mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId);
+        mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
     }
 
     /**
      * Removes a runtime association between the input device and display.
-     * @param inputPort The port of the input device.
+     * @param inputPort the port of the input device
      * <p>
      * Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
      * </p>
      * @hide
      */
+    @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+    @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     @TestApi
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
-        mGlobal.removeUniqueIdAssociation(inputPort);
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
+        mGlobal.removeUniqueIdAssociationByPort(inputPort);
+    }
+
+    /**
+     * Add a runtime association between the input device name and display, by descriptor. Input
+     * device descriptors are expected to be unique per physical device, though one physical
+     * device can have multiple virtual input devices that possess the same descriptor.
+     * E.g. a keyboard with built in trackpad will be 2 different input devices with the same
+     * descriptor.
+     * @param inputDeviceDescriptor the descriptor of the input device
+     * @param displayUniqueId the unique id of the associated display
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+     * </p>
+     * @hide
+     */
+    @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+    @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
+    @TestApi
+    public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+                                                   @NonNull String displayUniqueId) {
+        mGlobal.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+    }
+
+    /**
+     * Removes a runtime association between the input device and display.
+    }
+
+    /**
+     * Removes a runtime association between the input device and display.
+     * @param inputDeviceDescriptor the descriptor of the input device
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+     * </p>
+     * @hide
+     */
+    @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+    @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
+    @TestApi
+    public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+        mGlobal.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
     }
 
     /**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7b29666..1d253d9 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1467,22 +1467,46 @@
     }
 
     /**
-     * @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();
+        }
+    }
+
+    /**
+     * @see InputManager#addUniqueIdAssociationByDescriptor(String, String)
+     */
+    public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+                                                   @NonNull String displayUniqueId) {
+        try {
+            mIm.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see InputManager#removeUniqueIdAssociationByDescriptor(String)
+     */
+    public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+        try {
+            mIm.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 6246dd7..91cdf8d 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -124,6 +124,22 @@
             "vcn_network_selection_ipsec_packet_loss_percent_threshold";
 
     /**
+     * Key for detecting unusually large increases in IPsec packet sequence numbers.
+     *
+     * <p>If the sequence number increases by more than this value within a second, it may indicate
+     * an intentional leap on the server's downlink. To avoid false positives, the packet loss
+     * detector will suppress loss reporting.
+     *
+     * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
+     * To reduce false positives, consider setting an appropriate maximum threshold.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
+            "vcn_network_selection_max_seq_num_increase_per_second";
+
+    /**
      * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
      *
      * @hide
@@ -180,6 +196,7 @@
                 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
                 VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
                 VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
                 VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
                 VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
                 VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index e64823a..6fde398 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -34,4 +34,14 @@
     namespace: "vcn"
     description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change"
     bug: "323238888"
+}
+
+flag{
+    name: "handle_seq_num_leap"
+    namespace: "vcn"
+    description: "Do not report bad network when there is a suspected sequence number leap"
+    bug: "332598276"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9696dbc..9c14946 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -20,6 +20,7 @@
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.app.Notification;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -146,8 +147,13 @@
 
     /**
      * Data type: boolean, when true it suggests that the content text of this notification is
-     * sensitive. A notification listener can use this information to redact notifications on locked
-     * devices.
+     * sensitive. The system uses this information to improve privacy around the notification
+     * content. In {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, sensitive notification content is
+     * redacted from updates to most {@link NotificationListenerService
+     * NotificationListenerServices}. Also if an app posts a sensitive notification while
+     * {@link android.media.projection.MediaProjection screen-sharing} is active, that app's windows
+     * are blocked from screen-sharing and a {@link android.widget.Toast Toast} is shown to inform
+     * the user about this.
      */
     public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
 
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 188ad8f..56edfe72 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -103,9 +103,9 @@
             long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
     private static native void nativeDestroy(long nativeObject);
 
-    // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
-    // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
-    private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+    // 5KB is a balanced guess, since these are still pretty heavyweight objects, but if we make
+    // this too big, it can overwhelm the GC.
+    private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000;
 
     public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c95d6ff..a23df79 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -951,7 +951,7 @@
 
     private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
             boolean creating, boolean sizeChanged, boolean hintChanged, boolean relativeZChanged,
-            Transaction surfaceUpdateTransaction) {
+            boolean hdrHeadroomChanged, Transaction surfaceUpdateTransaction) {
         boolean realSizeChanged = false;
 
         mSurfaceLock.lock();
@@ -986,7 +986,7 @@
 
             updateBackgroundVisibility(surfaceUpdateTransaction);
             updateBackgroundColor(surfaceUpdateTransaction);
-            if (mLimitedHdrEnabled) {
+            if (mLimitedHdrEnabled && hdrHeadroomChanged) {
                 surfaceUpdateTransaction.setDesiredHdrHeadroom(
                         mBlastSurfaceControl, mHdrHeadroom);
             }
@@ -1203,7 +1203,7 @@
                 }
 
                 final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
-                        creating, sizeChanged, hintChanged, relativeZChanged,
+                        creating, sizeChanged, hintChanged, relativeZChanged, hdrHeadroomChanged,
                         surfaceUpdateTransaction);
 
                 try {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cacf0d2..ac1f646 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2428,6 +2428,26 @@
      */
     public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
 
+    /**
+     * This indicates that the frame rate category was chosen because it is currently boosting.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_BOOST = 0x0800_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it is currently having
+     * touch boost.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_TOUCH = 0x0900_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it is currently having
+     * touch boost.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_CONFLICTED = 0x0A00_0000;
+
     private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
 
     /**
@@ -5742,7 +5762,7 @@
      */
     private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
 
-    private static final float MAX_FRAME_RATE = 140;
+    static final float MAX_FRAME_RATE = 140;
 
     private static final int INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
     private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -33897,36 +33917,41 @@
         int height = mBottom - mTop;
 
         if (viewRootImpl != null && (width != 0 && height != 0)) {
-            if (mAttachInfo.mViewVelocityApi) {
-                float velocity = mFrameContentVelocity;
-                int mask = PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN;
-                float frameRate = 0;
+            if (viewRootImpl.shouldCheckFrameRate(mPreferredFrameRate > 0f)) {
+                float velocityFrameRate = 0f;
+                if (mAttachInfo.mViewVelocityApi) {
+                    float velocity = mFrameContentVelocity;
 
-                if (velocity < 0f
-                        && (mPrivateFlags4 & mask) == mask
-                        && mParent instanceof View
-                        && ((View) mParent).mFrameContentVelocity <= 0
-                ) {
-                    // This current calculation is very simple. If something on the screen moved,
-                    // then it votes for the highest velocity. If it doesn't move, then return 0.
-                    velocity = Float.POSITIVE_INFINITY;
-                    frameRate = MAX_FRAME_RATE;
-                }
-                if (velocity > 0f) {
-                    if (sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
-                        frameRate = convertVelocityToFrameRate(velocity);
+                    if (velocity < 0f
+                            && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
+                            PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)
+                            && mParent instanceof View
+                            && ((View) mParent).mFrameContentVelocity <= 0
+                    ) {
+                        // This current calculation is very simple. If something on the screen
+                        // moved, then it votes for the highest velocity.
+                        velocityFrameRate = MAX_FRAME_RATE;
+                    } else if (velocity > 0f) {
+                        velocityFrameRate = convertVelocityToFrameRate(velocity);
                     }
-                    viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
-                    return;
+                }
+                if (velocityFrameRate > 0f || mPreferredFrameRate > 0f) {
+                    int compatibility = FRAME_RATE_COMPATIBILITY_GTE;
+                    float frameRate = velocityFrameRate;
+                    if (mPreferredFrameRate > velocityFrameRate) {
+                        compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+                        frameRate = mPreferredFrameRate;
+                    }
+                    viewRootImpl.votePreferredFrameRate(frameRate, compatibility);
                 }
             }
-            if (!willNotDraw() && isDirty()) {
+            if (!willNotDraw() && isDirty() && viewRootImpl.shouldCheckFrameRateCategory()) {
                 if (sToolkitMetricsForFrameRateDecisionFlagValue) {
                     float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount;
                     viewRootImpl.recordViewPercentage(sizePercentage);
                 }
 
-                int frameRateCategory;
+                int frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                 if (Float.isNaN(mPreferredFrameRate)) {
                     frameRateCategory = calculateFrameRateCategory();
                 } else if (mPreferredFrameRate < 0) {
@@ -33951,10 +33976,6 @@
                                     | FRAME_RATE_CATEGORY_REASON_INVALID;
                         }
                     }
-                } else {
-                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
-                            mFrameRateCompatibility);
-                    return;
                 }
 
                 int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cd0602c..87dfa029 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -27,6 +27,7 @@
 import static android.view.DragEvent.ACTION_DRAG_LOCATION;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -34,14 +35,18 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_TOUCH;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
+import static android.view.View.MAX_FRAME_RATE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -1041,10 +1046,10 @@
 
     // The preferred frame rate category of the view that
     // could be updated on a frame-by-frame basis.
-    private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+    private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
     // The preferred frame rate category of the last frame that
     // could be used to lower frame rate after touch boost
-    private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+    private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
     // The preferred frame rate of the view that is mainly used for
     // touch boosting, view velocity handling, and TextureView.
     private float mPreferredFrameRate = 0;
@@ -4190,18 +4195,25 @@
         // For the variable refresh rate project.
         // We set the preferred frame rate and frame rate category at the end of performTraversals
         // when the values are applicable.
+        setCategoryFromCategoryCounts();
         setPreferredFrameRate(mPreferredFrameRate);
         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+        if (!mIsFrameRateConflicted) {
+            mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+            mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+                    FRAME_RATE_SETTING_REEVALUATE_TIME);
+        }
+        checkIdleness();
         mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0
                 ? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount;
         mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0
                 ? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount;
         mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0
                 ? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
-        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
         mPreferredFrameRate = -1;
-        mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
         mIsFrameRateConflicted = false;
+        mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
     }
 
     private void createSyncIfNeeded() {
@@ -6628,8 +6640,6 @@
                      */
                     mIsFrameRateBoosting = false;
                     mIsTouchBoosting = false;
-                    setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
-                            mLastPreferredFrameRateCategory));
                     break;
                 case MSG_CHECK_INVALIDATION_IDLE:
                     if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
@@ -7675,7 +7685,6 @@
                     mWindowAttributes.type)) {
                 // set the frame rate to the maximum value.
                 mIsTouchBoosting = true;
-                setPreferredFrameRateCategory(mPreferredFrameRateCategory);
             }
             /**
              * We want to lower the refresh rate when MotionEvent.ACTION_UP,
@@ -12467,58 +12476,50 @@
         EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
     }
 
+    /**
+     * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts.
+     */
+    private void setCategoryFromCategoryCounts() {
+        if (mFrameRateCategoryHighCount > 0) {
+            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+        } else if (mFrameRateCategoryHighHintCount > 0) {
+            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
+        } else if (mFrameRateCategoryNormalCount > 0) {
+            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL;
+        } else if (mFrameRateCategoryLowCount > 0) {
+            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW;
+        }
+    }
+
     private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
-        if (!shouldSetFrameRateCategory()
-                || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
-                && mPreferredFrameRate > 0
-                && sToolkitFrameRateVelocityMappingReadOnlyFlagValue)) {
+        if (!shouldSetFrameRateCategory()) {
             return;
         }
-        int categoryFromConflictedFrameRates = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-        if (mIsFrameRateConflicted) {
-            categoryFromConflictedFrameRates = mPreferredFrameRate > 60
-                    ? FRAME_RATE_CATEGORY_HIGH : FRAME_RATE_CATEGORY_NORMAL;
-        }
 
-        int frameRateCategory = mIsTouchBoosting
-                ? FRAME_RATE_CATEGORY_HIGH_HINT
-                : Math.max(preferredFrameRateCategory, categoryFromConflictedFrameRates);
+        int frameRateCategory;
+        int frameRateReason;
+        String view;
 
-        // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
-        // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
-        // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
-        // (e.g., Window Initialization).
-        if (mIsFrameRateBoosting || mInsetsAnimationRunning
-                || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
-                        && mPreferredFrameRate > 0)) {
+        if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
             frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
-            if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
-                // We've received a velocity, so we'll let the velocity control the
-                // frame rate unless we receive additional motion events.
-                mIsTouchBoosting = false;
-                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
-                mFrameRateCategoryView = null;
-            } else {
-                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
-            }
+            frameRateReason = FRAME_RATE_CATEGORY_REASON_BOOST;
+            view = null;
+        } else if (mIsTouchBoosting && preferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH_HINT) {
+            frameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
+            frameRateReason = FRAME_RATE_CATEGORY_REASON_TOUCH;
+            view = null;
+        } else {
+            frameRateCategory = preferredFrameRateCategory;
+            frameRateReason = mFrameRateCategoryChangeReason;
+            view = mFrameRateCategoryView;
         }
 
         try {
-            if (mLastPreferredFrameRateCategory != frameRateCategory) {
+            if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
+                    && mLastPreferredFrameRateCategory != frameRateCategory) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    String reason = reasonToString(mFrameRateCategoryChangeReason);
-                    String sourceView = mFrameRateCategoryView == null ? "-"
-                            : mFrameRateCategoryView;
-                    if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
-                        reason = "touch boost";
-                        sourceView = "-";
-                    } else if (categoryFromConflictedFrameRates == frameRateCategory
-                            && frameRateCategory != preferredFrameRateCategory
-                            && mIsFrameRateConflicted
-                    ) {
-                        reason = "conflict";
-                        sourceView = "-";
-                    }
+                    String reason = reasonToString(frameRateReason);
+                    String sourceView = view == null ? "-" : view;
                     String category = categoryToString(frameRateCategory);
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
@@ -12562,24 +12563,21 @@
             case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
             case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
             case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+            case FRAME_RATE_CATEGORY_REASON_BOOST -> str = "boost";
+            case FRAME_RATE_CATEGORY_REASON_TOUCH -> str = "touch";
+            case FRAME_RATE_CATEGORY_REASON_CONFLICTED -> str = "conflicted";
             default -> str = String.valueOf(reason);
         }
         return str;
     }
 
     private void setPreferredFrameRate(float preferredFrameRate) {
-        if (!shouldSetFrameRate()) {
-            return;
-        }
-        if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
-                && preferredFrameRate > 0 && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
-            mIsTouchBoosting = false;
+        if (!shouldSetFrameRate() || preferredFrameRate < 0) {
             return;
         }
 
         try {
-            if (mLastPreferredFrameRate != preferredFrameRate
-                    && preferredFrameRate >= 0) {
+            if (mLastPreferredFrameRate != preferredFrameRate) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate "
@@ -12588,7 +12586,7 @@
                 }
                 if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
                     mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
-                    mFrameRateCompatibility).applyAsyncUnsafe();
+                            mFrameRateCompatibility).applyAsyncUnsafe();
                 }
                 mLastPreferredFrameRate = preferredFrameRate;
             }
@@ -12599,12 +12597,6 @@
         }
     }
 
-    private void sendDelayedEmptyMessage(int message, int delayedTime) {
-        mHandler.removeMessages(message);
-
-        mHandler.sendEmptyMessageDelayed(message, delayedTime);
-    }
-
     private boolean shouldSetFrameRateCategory() {
         // use toolkitSetFrameRate flag to gate the change
         return  mSurface.isValid() && shouldEnableDvrr();
@@ -12642,25 +12634,34 @@
             case FRAME_RATE_CATEGORY_HIGH ->
                     mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
         }
-
-        int oldCategory = mPreferredFrameRateCategory;
-        if (mFrameRateCategoryHighCount > 0) {
-            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
-        } else if (mFrameRateCategoryHighHintCount > 0) {
-            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
-        } else if (mFrameRateCategoryNormalCount > 0) {
-            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL;
-        } else if (mFrameRateCategoryLowCount > 0) {
-            mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW;
+        if (frameRateCategory > mPreferredFrameRateCategory) {
+            mPreferredFrameRateCategory = frameRateCategory;
+            mFrameRateCategoryChangeReason = reason;
+            mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName();
         }
         mHasInvalidation = true;
-        checkIdleness();
-        if (mPreferredFrameRateCategory != oldCategory
-                && mPreferredFrameRateCategory == frameRateCategory
-        ) {
-            mFrameRateCategoryChangeReason = reason;
-            mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName();
-        }
+    }
+
+    /**
+     * Returns whether a View should vote for frame rate category. When the category is HIGH
+     * already, there's no need to calculate the category on the View and vote.
+     */
+    public boolean shouldCheckFrameRateCategory() {
+        return mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH;
+    }
+
+    /**
+     * Returns whether a View should vote for frame rate. When the maximum frame rate has already
+     * been voted for, there's no point in calculating and voting for the frame rate. When
+     * isDirect is false, then it will return false when the velocity-calculated frame rate
+     * can be avoided.
+     * @param isDirect true when the frame rate has been set directly on the View or false if
+     *                 the calculation is based only on velocity.
+     */
+    public boolean shouldCheckFrameRate(boolean isDirect) {
+        return mPreferredFrameRate < MAX_FRAME_RATE
+                || (!isDirect && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue
+                && mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH);
     }
 
     /**
@@ -12686,24 +12687,44 @@
         if (frameRate <= 0) {
             return;
         }
+        if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+            mIsTouchBoosting = false;
+            if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
+                mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+                mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+                mFrameRateCategoryView = null;
+                return;
+            }
+        }
+        float nextFrameRate;
+        int nextFrameRateCompatibility;
+        if (frameRate > mPreferredFrameRate) {
+            nextFrameRate = frameRate;
+            nextFrameRateCompatibility = frameRateCompatibility;
+        } else {
+            nextFrameRate = mPreferredFrameRate;
+            nextFrameRateCompatibility = mFrameRateCompatibility;
+        }
+
         if (mPreferredFrameRate > 0 && mPreferredFrameRate % frameRate != 0
                 && frameRate % mPreferredFrameRate != 0) {
             mIsFrameRateConflicted = true;
-            mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-        }
-        if (frameRate > mPreferredFrameRate) {
-            mFrameRateCompatibility = frameRateCompatibility;
+            if (nextFrameRate > 60 && mFrameRateCategoryHighCount != FRAME_RATE_CATEGORY_COUNT) {
+                mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED;
+                mFrameRateCategoryView = null;
+            } else if (mFrameRateCategoryHighCount == 0 && mFrameRateCategoryHighHintCount == 0
+                    && mFrameRateCategoryNormalCount < FRAME_RATE_CATEGORY_COUNT) {
+                mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT;
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED;
+                mFrameRateCategoryView = null;
+            }
         }
 
-        mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate);
+        mPreferredFrameRate = nextFrameRate;
+        mFrameRateCompatibility = nextFrameRateCompatibility;
         mHasInvalidation = true;
-
-        if (!mIsFrameRateConflicted) {
-            mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
-            mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
-                    FRAME_RATE_SETTING_REEVALUATE_TIME);
-        }
-        checkIdleness();
     }
 
     /**
@@ -12773,7 +12794,6 @@
 
     private void boostFrameRate(int boostTimeOut) {
         mIsFrameRateBoosting = true;
-        setPreferredFrameRateCategory(mPreferredFrameRateCategory);
         mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
         mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT,
                 boostTimeOut);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 8f1b72e..1034479 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -40,6 +40,7 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 
 import java.io.File;
 import java.lang.reflect.Method;
@@ -609,7 +610,7 @@
             startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
         } catch (Throwable t) {
             // Log and discard errors at this stage as we must not crash the system server.
-            Log.e(LOGTAG, "error preparing webview native library", t);
+            Slog.wtf(LOGTAG, "error preparing webview native library", t);
         }
 
         WebViewZygote.onWebViewProviderChanged(packageInfo);
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index a68a577..1a8745c 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 import android.os.Process;
 import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
@@ -137,7 +138,7 @@
             if (!success) throw new Exception("Failed to start the relro file creator process");
         } catch (Throwable t) {
             // Log and discard errors as we must not crash the system server.
-            Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
+            Slog.wtf(LOGTAG, "error starting relro file creator for abi " + abi, t);
             crashHandler.run();
         }
     }
diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl
index ec8b66d..33421ba 100644
--- a/core/java/android/window/IRemoteTransition.aidl
+++ b/core/java/android/window/IRemoteTransition.aidl
@@ -19,6 +19,7 @@
 import android.view.SurfaceControl;
 import android.window.IRemoteTransitionFinishedCallback;
 import android.window.TransitionInfo;
+import android.window.WindowAnimationState;
 
 /**
  * Interface allowing remote processes to play transition animations.
@@ -61,6 +62,19 @@
             in IRemoteTransitionFinishedCallback finishCallback);
 
     /**
+     * Takes over the animation of the windows from an existing transition. Once complete, the
+     * implementation should call `finishCallback`.
+     *
+     * @param transition An identifier for the transition to be taken over.
+     * @param states The animation states of the windows involved in the transition. These must be
+     *               sorted in the same way as the Changes inside `info`, and each state may be
+     *               null.
+     */
+    void takeOverAnimation(in IBinder transition, in TransitionInfo info,
+            in SurfaceControl.Transaction t, in IRemoteTransitionFinishedCallback finishCallback,
+            in WindowAnimationState[] states);
+
+    /**
      * Called when a different handler has consumed the transition
      *
      * @param transition An identifier for the transition that was consumed.
diff --git a/core/java/android/window/RemoteTransitionStub.java b/core/java/android/window/RemoteTransitionStub.java
index c9932ab..4a97b1e 100644
--- a/core/java/android/window/RemoteTransitionStub.java
+++ b/core/java/android/window/RemoteTransitionStub.java
@@ -31,6 +31,16 @@
             SurfaceControl.Transaction t, IBinder mergeTarget,
             IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {}
 
+
+    @Override
+    public void takeOverAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction startTransaction,
+            IRemoteTransitionFinishedCallback finishCallback,
+            WindowAnimationState[] states) throws RemoteException {
+        throw new RemoteException("Takeovers are not supported by this IRemoteTransition");
+    }
+
+
     @Override
     public void onTransitionConsumed(IBinder transition, boolean aborted)
             throws RemoteException {}
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 93297e6..89327fe 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -18,10 +18,13 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WindowingMode;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -101,11 +104,20 @@
      */
     private final boolean mAllowTransitionWhenEmpty;
 
+    /**
+     * The override orientation for the TaskFragment. This is effective only for a system organizer.
+     * The value is ignored otherwise. Default to {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+     *
+     * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+     */
+    private final @ScreenOrientation int mOverrideOrientation;
+
     private TaskFragmentCreationParams(
             @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
             @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
-            @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
+            @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
+            @ScreenOrientation int overrideOrientation) {
         if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
             throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
                     + " pairedActivityToken should not be set at the same time.");
@@ -118,6 +130,7 @@
         mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
         mPairedActivityToken = pairedActivityToken;
         mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+        mOverrideOrientation = overrideOrientation;
     }
 
     @NonNull
@@ -168,6 +181,11 @@
         return mAllowTransitionWhenEmpty;
     }
 
+    /** @hide */
+    public @ScreenOrientation int getOverrideOrientation() {
+        return mOverrideOrientation;
+    }
+
     private TaskFragmentCreationParams(Parcel in) {
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
@@ -177,6 +195,7 @@
         mPairedPrimaryFragmentToken = in.readStrongBinder();
         mPairedActivityToken = in.readStrongBinder();
         mAllowTransitionWhenEmpty = in.readBoolean();
+        mOverrideOrientation = in.readInt();
     }
 
     /** @hide */
@@ -190,6 +209,7 @@
         dest.writeStrongBinder(mPairedPrimaryFragmentToken);
         dest.writeStrongBinder(mPairedActivityToken);
         dest.writeBoolean(mAllowTransitionWhenEmpty);
+        dest.writeInt(mOverrideOrientation);
     }
 
     @NonNull
@@ -217,6 +237,7 @@
                 + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
                 + " pairedActivityToken=" + mPairedActivityToken
                 + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+                + " overrideOrientation=" + mOverrideOrientation
                 + "}";
     }
 
@@ -252,6 +273,8 @@
 
         private boolean mAllowTransitionWhenEmpty;
 
+        private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
         public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
@@ -330,12 +353,28 @@
             return this;
         }
 
+        /**
+         * Sets the override orientation for the TaskFragment. This is effective only for a system
+         * organizer. The value is ignored otherwise. Default to
+         * {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+         *
+         * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+         *
+         * @hide
+         */
+        @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+        @NonNull
+        public Builder setOverrideOrientation(@ScreenOrientation int overrideOrientation) {
+            mOverrideOrientation = overrideOrientation;
+            return this;
+        }
+
         /** Constructs the options to create TaskFragment with. */
         @NonNull
         public TaskFragmentCreationParams build() {
             return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
                     mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
-                    mPairedActivityToken, mAllowTransitionWhenEmpty);
+                    mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
         }
     }
 }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 994e732..bcae571 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -523,6 +523,9 @@
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
         }
+        if ((flags & FLAG_CONFIG_AT_END) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("CONFIG_AT_END");
+        }
         if ((flags & FLAG_MOVED_TO_TOP) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
         }
diff --git a/core/java/android/window/WindowAnimationState.aidl b/core/java/android/window/WindowAnimationState.aidl
new file mode 100644
index 0000000..e662860
--- /dev/null
+++ b/core/java/android/window/WindowAnimationState.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.window;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * Properties of a window animation at a given point in time.
+ *
+ * {@hide}
+ */
+parcelable WindowAnimationState {
+    long timestamp;
+    RectF bounds;
+    float scale;
+    float topLeftRadius;
+    float topRightRadius;
+    float bottomRightRadius;
+    float bottomLeftRadius;
+    PointF velocityPxPerMs;
+}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bcbac93..47a4052 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -36,6 +36,8 @@
 import androidx.annotation.VisibleForTesting;
 
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -75,14 +77,17 @@
     @Nullable
     private ImeBackAnimationController mImeBackAnimationController;
 
+    @GuardedBy("mLock")
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
     /** Holds all callbacks by priorities. */
 
     @VisibleForTesting
+    @GuardedBy("mLock")
     public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
             mOnBackInvokedCallbacks = new TreeMap<>();
     private Checker mChecker;
+    private final Object mLock = new Object();
 
     public WindowOnBackInvokedDispatcher(@NonNull Context context) {
         mChecker = new Checker(context);
@@ -94,20 +99,24 @@
      */
     public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
             @Nullable ImeBackAnimationController imeBackAnimationController) {
-        mWindowSession = windowSession;
-        mWindow = window;
-        mImeBackAnimationController = imeBackAnimationController;
-        if (!mAllCallbacks.isEmpty()) {
-            setTopOnBackInvokedCallback(getTopCallback());
+        synchronized (mLock) {
+            mWindowSession = windowSession;
+            mWindow = window;
+            mImeBackAnimationController = imeBackAnimationController;
+            if (!mAllCallbacks.isEmpty()) {
+                setTopOnBackInvokedCallback(getTopCallback());
+            }
         }
     }
 
     /** Detaches the dispatcher instance from its window. */
     public void detachFromWindow() {
-        clear();
-        mWindow = null;
-        mWindowSession = null;
-        mImeBackAnimationController = null;
+        synchronized (mLock) {
+            clear();
+            mWindow = null;
+            mWindowSession = null;
+            mImeBackAnimationController = null;
+        }
     }
 
     // TODO: Take an Executor for the callback to run on.
@@ -125,65 +134,71 @@
      */
     public void registerOnBackInvokedCallbackUnchecked(
             @NonNull OnBackInvokedCallback callback, @Priority int priority) {
-        if (mImeDispatcher != null) {
-            mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
-            return;
-        }
-        if (!mOnBackInvokedCallbacks.containsKey(priority)) {
-            mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
-        }
-        if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
-            callback = mImeBackAnimationController;
-        }
-        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-
-        // If callback has already been added, remove it and re-add it.
-        if (mAllCallbacks.containsKey(callback)) {
-            if (DEBUG) {
-                Log.i(TAG, "Callback already added. Removing and re-adding it.");
+        synchronized (mLock) {
+            if (mImeDispatcher != null) {
+                mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+                return;
             }
-            Integer prevPriority = mAllCallbacks.get(callback);
-            mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
-        }
+            if (!mOnBackInvokedCallbacks.containsKey(priority)) {
+                mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
+            }
+            if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+                callback = mImeBackAnimationController;
+            }
+            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
 
-        OnBackInvokedCallback previousTopCallback = getTopCallback();
-        callbacks.add(callback);
-        mAllCallbacks.put(callback, priority);
-        if (previousTopCallback == null
-                || (previousTopCallback != callback
-                        && mAllCallbacks.get(previousTopCallback) <= priority)) {
-            setTopOnBackInvokedCallback(callback);
+            // If callback has already been added, remove it and re-add it.
+            if (mAllCallbacks.containsKey(callback)) {
+                if (DEBUG) {
+                    Log.i(TAG, "Callback already added. Removing and re-adding it.");
+                }
+                Integer prevPriority = mAllCallbacks.get(callback);
+                mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
+            }
+
+            OnBackInvokedCallback previousTopCallback = getTopCallback();
+            callbacks.add(callback);
+            mAllCallbacks.put(callback, priority);
+            if (previousTopCallback == null
+                    || (previousTopCallback != callback
+                    && mAllCallbacks.get(previousTopCallback) <= priority)) {
+                setTopOnBackInvokedCallback(callback);
+            }
         }
     }
 
     @Override
     public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
-        if (mImeDispatcher != null) {
-            mImeDispatcher.unregisterOnBackInvokedCallback(callback);
-            return;
-        }
-        if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
-            callback = mImeBackAnimationController;
-        }
-        if (!mAllCallbacks.containsKey(callback)) {
-            if (DEBUG) {
-                Log.i(TAG, "Callback not found. returning...");
+        synchronized (mLock) {
+            if (mImeDispatcher != null) {
+                mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+                return;
             }
-            return;
-        }
-        OnBackInvokedCallback previousTopCallback = getTopCallback();
-        Integer priority = mAllCallbacks.get(callback);
-        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-        callbacks.remove(callback);
-        if (callbacks.isEmpty()) {
-            mOnBackInvokedCallbacks.remove(priority);
-        }
-        mAllCallbacks.remove(callback);
-        // Re-populate the top callback to WM if the removed callback was previously the top one.
-        if (previousTopCallback == callback) {
-            // We should call onBackCancelled() when an active callback is removed from dispatcher.
-            sendCancelledIfInProgress(callback);
-            setTopOnBackInvokedCallback(getTopCallback());
+            if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+                callback = mImeBackAnimationController;
+            }
+            if (!mAllCallbacks.containsKey(callback)) {
+                if (DEBUG) {
+                    Log.i(TAG, "Callback not found. returning...");
+                }
+                return;
+            }
+            OnBackInvokedCallback previousTopCallback = getTopCallback();
+            Integer priority = mAllCallbacks.get(callback);
+            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+            callbacks.remove(callback);
+            if (callbacks.isEmpty()) {
+                mOnBackInvokedCallbacks.remove(priority);
+            }
+            mAllCallbacks.remove(callback);
+            // Re-populate the top callback to WM if the removed callback was previously the top
+            // one.
+            if (previousTopCallback == callback) {
+                // We should call onBackCancelled() when an active callback is removed from
+                // dispatcher.
+                sendCancelledIfInProgress(callback);
+                setTopOnBackInvokedCallback(getTopCallback());
+            }
         }
     }
 
@@ -191,15 +206,21 @@
      * Indicates if the dispatcher is actively dispatching to a callback.
      */
     public boolean isDispatching() {
-        return mIsDispatching;
+        synchronized (mLock) {
+            return mIsDispatching;
+        }
     }
 
     private void onStartDispatching() {
-        mIsDispatching = true;
+        synchronized (mLock) {
+            mIsDispatching = true;
+        }
     }
 
     private void onStopDispatching() {
-        mIsDispatching = false;
+        synchronized (mLock) {
+            mIsDispatching = false;
+        }
     }
 
     private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
@@ -223,27 +244,29 @@
 
     /** Clears all registered callbacks on the instance. */
     public void clear() {
-        if (mImeDispatcher != null) {
-            mImeDispatcher.clear();
-            mImeDispatcher = null;
-        }
-        if (!mAllCallbacks.isEmpty()) {
-            OnBackInvokedCallback topCallback = getTopCallback();
-            if (topCallback != null) {
-                sendCancelledIfInProgress(topCallback);
-            } else {
-                // Should not be possible
-                Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+        synchronized (mLock) {
+            if (mImeDispatcher != null) {
+                mImeDispatcher.clear();
+                mImeDispatcher = null;
             }
-            // Clear binder references in WM.
-            setTopOnBackInvokedCallback(null);
-        }
+            if (!mAllCallbacks.isEmpty()) {
+                OnBackInvokedCallback topCallback = getTopCallback();
+                if (topCallback != null) {
+                    sendCancelledIfInProgress(topCallback);
+                } else {
+                    // Should not be possible
+                    Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+                }
+                // Clear binder references in WM.
+                setTopOnBackInvokedCallback(null);
+            }
 
-        // We should also stop running animations since all callbacks have been removed.
-        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
-        Handler.getMain().post(mProgressAnimator::reset);
-        mAllCallbacks.clear();
-        mOnBackInvokedCallbacks.clear();
+            // We should also stop running animations since all callbacks have been removed.
+            // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+            Handler.getMain().post(mProgressAnimator::reset);
+            mAllCallbacks.clear();
+            mOnBackInvokedCallbacks.clear();
+        }
     }
 
     private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -275,13 +298,15 @@
     }
 
     public OnBackInvokedCallback getTopCallback() {
-        if (mAllCallbacks.isEmpty()) {
-            return null;
-        }
-        for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
-            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-            if (!callbacks.isEmpty()) {
-                return callbacks.get(callbacks.size() - 1);
+        synchronized (mLock) {
+            if (mAllCallbacks.isEmpty()) {
+                return null;
+            }
+            for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+                ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+                if (!callbacks.isEmpty()) {
+                    return callbacks.get(callbacks.size() - 1);
+                }
             }
         }
         return null;
@@ -315,16 +340,18 @@
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "    ";
         writer.println(prefix + "WindowOnBackDispatcher:");
-        if (mAllCallbacks.isEmpty()) {
-            writer.println(prefix + "<None>");
-            return;
-        }
+        synchronized (mLock) {
+            if (mAllCallbacks.isEmpty()) {
+                writer.println(prefix + "<None>");
+                return;
+            }
 
-        writer.println(innerPrefix + "Top Callback: " + getTopCallback());
-        writer.println(innerPrefix + "Callbacks: ");
-        mAllCallbacks.forEach((callback, priority) -> {
-            writer.println(innerPrefix + "  Callback: " + callback + " Priority=" + priority);
-        });
+            writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+            writer.println(innerPrefix + "Callbacks: ");
+            mAllCallbacks.forEach((callback, priority) -> {
+                writer.println(innerPrefix + "  Callback: " + callback + " Priority=" + priority);
+            });
+        }
     }
 
     static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 4bd2d72..01920de 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -42,7 +42,11 @@
 bool punchHoles(const char *filePath, const uint64_t offset,
                 const std::vector<Elf64_Phdr> &programHeaders) {
     struct stat64 beforePunch;
-    lstat64(filePath, &beforePunch);
+    if (int result = lstat64(filePath, &beforePunch); result != 0) {
+        ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+        return false;
+    }
+
     uint64_t blockSize = beforePunch.st_blksize;
     IF_ALOGD() {
         ALOGD("Total number of LOAD segments %zu", programHeaders.size());
@@ -152,7 +156,10 @@
 
     IF_ALOGD() {
         struct stat64 afterPunch;
-        lstat64(filePath, &afterPunch);
+        if (int result = lstat64(filePath, &afterPunch); result != 0) {
+            ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+            return false;
+        }
         ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
               "",
               afterPunch.st_blocks, afterPunch.st_blksize,
@@ -177,7 +184,7 @@
 
     // only consider elf64 for punching holes
     if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
-        ALOGE("Provided file is not ELF64");
+        ALOGW("Provided file is not ELF64");
         return false;
     }
 
@@ -215,4 +222,108 @@
     return punchHoles(filePath, offset, programHeaders);
 }
 
+bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) {
+    android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
+    if (!fd.ok()) {
+        ALOGE("Can't open file to punch %s", filePath);
+        return false;
+    }
+
+    struct stat64 beforePunch;
+    if (int result = lstat64(filePath, &beforePunch); result != 0) {
+        ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+        return false;
+    }
+
+    uint64_t blockSize = beforePunch.st_blksize;
+    IF_ALOGD() {
+        ALOGD("Extra field length: %hu,  Size before punching holes st_blocks: %" PRIu64
+              ", st_blksize: %ld, st_size: %" PRIu64 "",
+              extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
+              static_cast<uint64_t>(beforePunch.st_size));
+    }
+
+    if (extraFieldLen < blockSize) {
+        ALOGD("Skipping punching apk as extra field length is less than block size");
+        return false;
+    }
+
+    // content is preceded by extra field. Zip offset is offset of exact content.
+    // move back by extraFieldLen so that scan can be started at start of extra field.
+    uint64_t extraFieldStart;
+    if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) {
+        ALOGE("Overflow occurred when calculating start of extra field");
+        return false;
+    }
+
+    constexpr uint64_t kMaxSize = 64 * 1024;
+    // Use malloc to gracefully handle any oom conditions
+    std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)),
+                                                     &free);
+    if (buffer == nullptr) {
+        ALOGE("Failed to allocate read buffer");
+        return false;
+    }
+
+    // Read the entire extra fields at once and punch file according to zero stretches.
+    if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) {
+        ALOGE("Failed to read extra field content");
+        return false;
+    }
+
+    IF_ALOGD() {
+        ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen,
+              HexString(buffer.get(), extraFieldLen).c_str());
+    }
+
+    uint64_t currentSize = 0;
+    while (currentSize < extraFieldLen) {
+        uint64_t end = currentSize;
+        // find zero ranges
+        while (end < extraFieldLen && *(buffer.get() + end) == 0) {
+            ++end;
+        }
+
+        uint64_t punchLen;
+        if (__builtin_sub_overflow(end, currentSize, &punchLen)) {
+            ALOGW("Overflow occurred when calculating punching length");
+            return false;
+        }
+
+        // Don't punch for every stretch of zero which is found
+        if (punchLen > blockSize) {
+            uint64_t punchOffset;
+            if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) {
+                ALOGW("Overflow occurred when calculating punch start offset");
+                return false;
+            }
+
+            ALOGD("Punching hole in apk start: %" PRIu64 " len:%" PRIu64 "", punchOffset, punchLen);
+
+            // Punch hole for this entire stretch.
+            int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchOffset,
+                                   punchLen);
+            if (result < 0) {
+                ALOGE("fallocate failed to punch hole inside apk, error:%d", errno);
+                return false;
+            }
+        }
+        currentSize = end;
+        ++currentSize;
+    }
+
+    IF_ALOGD() {
+        struct stat64 afterPunch;
+        if (int result = lstat64(filePath, &afterPunch); result != 0) {
+            ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+            return false;
+        }
+        ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
+              ", st_blksize: %ld, st_size: %" PRIu64 "",
+              afterPunch.st_blocks, afterPunch.st_blksize,
+              static_cast<uint64_t>(afterPunch.st_size));
+    }
+    return true;
+}
+
 }; // namespace android
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
index a6b145c..52445e2 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -28,4 +28,11 @@
  */
 bool punchHolesInElf64(const char* filePath, uint64_t offset);
 
+/*
+ * This function punches holes in zero segments of Apk file which are introduced during the
+ * alignment. Alignment tools add padding inside of extra field in local file header. punch holes in
+ * extra field for zero stretches till the actual file content.
+ */
+bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
+
 } // namespace android
\ No newline at end of file
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index faa83f8..9b8dab7 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -28,6 +28,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/statfs.h>
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
@@ -145,8 +146,9 @@
 
     uint16_t method;
     off64_t offset;
-
-    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) {
+    uint16_t extraFieldLength;
+    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
+                               &extraFieldLength)) {
         ALOGE("Couldn't read zip entry info\n");
         return INSTALL_FAILED_INVALID_APK;
     }
@@ -177,6 +179,12 @@
                   "%" PRIu64 "",
                   fileName, zipFile->getZipFileName(), offset);
         }
+
+        // if extra field for this zip file is present with some length, possibility is that it is
+        // padding added for zip alignment. Punch holes there too.
+        if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
+            ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
+        }
 #endif // ENABLE_PUNCH_HOLES
 
         return INSTALL_SUCCEEDED;
@@ -279,6 +287,25 @@
         return INSTALL_FAILED_CONTAINER_ERROR;
     }
 
+#ifdef ENABLE_PUNCH_HOLES
+    // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
+    // will be useful for ext4 based systems
+    struct statfs64 fsInfo;
+    int result = statfs64(localFileName, &fsInfo);
+    if (result < 0) {
+        ALOGW("Failed to stat file :%s", localFileName);
+    }
+
+    if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) {
+        ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName,
+              static_cast<uint64_t>(fsInfo.f_type));
+        if (!punchHolesInElf64(localFileName, 0)) {
+            ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName,
+                  zipFile->getZipFileName());
+        }
+    }
+#endif // ENABLE_PUNCH_HOLES
+
     ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
 
     return INSTALL_SUCCEEDED;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c694426..0864210 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -581,6 +581,7 @@
 
     <protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+    <protected-broadcast android:name="com.android.server.notification.TimeToLiveHelper" />
     <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
     <protected-broadcast android:name="EventConditionProvider.EVALUATE" />
@@ -5889,7 +5890,7 @@
     <!-- Allows an application to subscribe to notifications about the nearby devices' presence
          status change base on the UUIDs.
          <p>Not for use by third-party applications.</p>
-         @FlaggedApi("android.companion.flags.device_presence")
+         @FlaggedApi("android.companion.device_presence")
     -->
     <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
                 android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable/ic_private_profile_badge.xml b/core/res/res/drawable/ic_private_profile_badge.xml
index b042c39..8f7bbb5 100644
--- a/core/res/res/drawable/ic_private_profile_badge.xml
+++ b/core/res/res/drawable/ic_private_profile_badge.xml
@@ -20,6 +20,10 @@
         android:viewportWidth="24"
         android:viewportHeight="24">
         <path
-            android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"
+            android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
+            android:fillColor="#3C4043"
+            android:fillType="evenOdd"/>
+        <path
+            android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
             android:fillColor="#3C4043"/>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_private_profile_icon_badge.xml b/core/res/res/drawable/ic_private_profile_icon_badge.xml
index 5f1f1b7..4143c3b 100644
--- a/core/res/res/drawable/ic_private_profile_icon_badge.xml
+++ b/core/res/res/drawable/ic_private_profile_icon_badge.xml
@@ -24,8 +24,12 @@
         android:scaleY=".66"
         android:translateX="42"
         android:translateY="42">
-            <path
-                android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"
-                android:fillColor="#3C4043"/>
+        <path
+            android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
+            android:fillColor="#3C4043"
+            android:fillType="evenOdd"/>
+        <path
+            android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
+            android:fillColor="#3C4043"/>
     </group>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/stat_sys_private_profile_status.xml b/core/res/res/drawable/stat_sys_private_profile_status.xml
index 429070e..958a4d7 100644
--- a/core/res/res/drawable/stat_sys_private_profile_status.xml
+++ b/core/res/res/drawable/stat_sys_private_profile_status.xml
@@ -20,6 +20,10 @@
         android:viewportWidth="24"
         android:viewportHeight="24">
     <path
+        android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
         android:fillColor="@android:color/white"
-        android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"/>
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
+        android:fillColor="@android:color/white"/>
 </vector>
\ No newline at end of file
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 404e873..04e90ba 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -154,6 +154,12 @@
         "android.test.runner",
         "org.apache.http.legacy",
     ],
+    uses_libs: [
+        "android.test.runner",
+    ],
+    optional_uses_libs: [
+        "org.apache.http.legacy",
+    ],
     sdk_version: "core_platform",
 }
 
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 28343f1..0bf9a4c 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -203,7 +203,9 @@
         mActivityRule.runOnUiThread(() -> {
             mMovingView.setFrameContentVelocity(1_000_000_000f);
             mMovingView.invalidate();
-            runAfterDraw(() -> assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f));
+            runAfterDraw(() -> {
+                assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f);
+            });
         });
         waitForAfterDraw();
     }
@@ -411,6 +413,26 @@
         waitForAfterDraw();
     }
 
+    @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY
+    })
+    public void frameRateAndCategory() throws Throwable {
+        waitForFrameRateCategoryToSettle();
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+            mMovingView.setFrameContentVelocity(1f);
+            mMovingView.invalidate();
+            runAfterDraw(() -> {
+                assertEquals(FRAME_RATE_CATEGORY_LOW,
+                        mViewRoot.getLastPreferredFrameRateCategory());
+                assertEquals(60f, mViewRoot.getLastPreferredFrameRate());
+            });
+        });
+        waitForAfterDraw();
+    }
+
     private void runAfterDraw(@NonNull Runnable runnable) {
         Handler handler = new Handler(Looper.getMainLooper());
         mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index e6e7e8c..80fef6c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -22,6 +22,7 @@
 import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
 import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -51,6 +52,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -495,13 +497,13 @@
     @UiThreadTest
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_getDefaultValues() {
         ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
                 sContext.getDisplayNoVerify());
-        assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
-                FRAME_RATE_CATEGORY_NO_PREFERENCE);
-        assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0, 0.1);
+        assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                viewRootImpl.getLastPreferredFrameRateCategory());
+        assertEquals(0, viewRootImpl.getLastPreferredFrameRate(), 0.1);
     }
 
     /**
@@ -513,7 +515,7 @@
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() throws Throwable {
         mView = new View(sContext);
         attachViewToWindow(mView);
@@ -538,7 +540,7 @@
         sInstrumentation.waitForIdleSync();
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getIsFrameRateBoosting(), true);
+            assertTrue(mViewRootImpl.getIsFrameRateBoosting());
         });
     }
 
@@ -550,7 +552,7 @@
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() throws Throwable {
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -568,8 +570,8 @@
         waitForFrameRateCategoryToSettle(mView);
         sInstrumentation.runOnMainSync(() -> {
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_LOW));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
     }
@@ -582,7 +584,7 @@
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() throws Throwable {
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -655,7 +657,7 @@
         sInstrumentation.waitForIdleSync();
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getIsFrameRateBoosting(), true);
+            assertTrue(mViewRootImpl.getIsFrameRateBoosting());
         });
 
         waitForFrameRateCategoryToSettle(mView);
@@ -750,14 +752,14 @@
      */
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
         View mView1 = new View(sContext);
         attachViewToWindow(mView1);
         ViewRootImpl viewRootImpl = mView1.getViewRootImpl();
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                    viewRootImpl.getPreferredFrameRateCategory());
         });
 
         // reset the frame rate category counts
@@ -771,20 +773,20 @@
 
         sInstrumentation.runOnMainSync(() -> {
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+            assertEquals(FRAME_RATE_CATEGORY_LOW, viewRootImpl.getPreferredFrameRateCategory());
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+            assertEquals(FRAME_RATE_CATEGORY_NORMAL, viewRootImpl.getPreferredFrameRateCategory());
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_HIGH_HINT);
+            assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT,
+                    viewRootImpl.getPreferredFrameRateCategory());
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
         });
     }
 
@@ -796,55 +798,67 @@
      */
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRate_aggregate() {
         mView = new View(sContext);
         attachViewToWindow(mView);
         mViewRootImpl = mView.getViewRootImpl();
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 0, 0.1);
+            assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
             assertEquals(mViewRootImpl.getFrameRateCompatibility(),
                     FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+            assertFalse(mViewRootImpl.isFrameRateConflicted());
             mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 24, 0.1);
-            assertEquals(mViewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_GTE);
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+            if (toolkitFrameRateVelocityMappingReadOnly()) {
+                assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1);
+                assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+                        mViewRootImpl.getFrameRateCompatibility());
+                assertFalse(mViewRootImpl.isFrameRateConflicted());
+            } else {
+                assertEquals(FRAME_RATE_CATEGORY_HIGH,
+                        mViewRootImpl.getPreferredFrameRateCategory());
+            }
             mViewRootImpl.votePreferredFrameRate(30, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 30, 0.1);
+            assertEquals(30, mViewRootImpl.getPreferredFrameRate(), 0.1);
             // If there is a conflict, then set compatibility to
             // FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
-            assertEquals(mViewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            // Should be true since there is a conflict between 24 and 30.
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), true);
+            assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    mViewRootImpl.getFrameRateCompatibility());
+            if (toolkitFrameRateVelocityMappingReadOnly()) {
+                // Should be true since there is a conflict between 24 and 30.
+                assertTrue(mViewRootImpl.isFrameRateConflicted());
+            }
+
             mView.invalidate();
         });
         sInstrumentation.waitForIdleSync();
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+            assertFalse(mViewRootImpl.isFrameRateConflicted());
             mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 60, 0.1);
-            assertEquals(mViewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_GTE);
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+            if (toolkitFrameRateVelocityMappingReadOnly()) {
+                assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1);
+                assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+                        mViewRootImpl.getFrameRateCompatibility());
+            } else {
+                assertEquals(FRAME_RATE_CATEGORY_HIGH,
+                        mViewRootImpl.getPreferredFrameRateCategory());
+            }
+            assertFalse(mViewRootImpl.isFrameRateConflicted());
             mViewRootImpl.votePreferredFrameRate(120, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 120, 0.1);
-            assertEquals(mViewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+            assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
+            assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    mViewRootImpl.getFrameRateCompatibility());
             // Should be false since 60 is a divisor of 120.
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+            assertFalse(mViewRootImpl.isFrameRateConflicted());
             mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 120, 0.1);
+            assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
             // compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
             // since the frame rate 60 is smaller than 120.
-            assertEquals(mViewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+            assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    mViewRootImpl.getFrameRateCompatibility());
             // Should be false since 60 is a divisor of 120.
-            assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
-
+            assertFalse(mViewRootImpl.isFrameRateConflicted());
         });
     }
 
@@ -864,14 +878,14 @@
 
         mViewRootImpl = mView.getViewRootImpl();
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                    mViewRootImpl.getPreferredFrameRateCategory());
         });
 
         // reset the frame rate category counts
         for (int i = 0; i < 5; i++) {
             sInstrumentation.runOnMainSync(() -> {
-                mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+                mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
                 mView.invalidate();
             });
             sInstrumentation.waitForIdleSync();
@@ -880,24 +894,24 @@
         waitForFrameRateCategoryToSettle(mView);
 
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_LOW));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NORMAL));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_HIGH));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
     }
@@ -928,22 +942,23 @@
         waitForFrameRateCategoryToSettle(mView);
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getPreferredFrameRate(), 0, 0.1);
+            assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
             mView.setFrameContentVelocity(100);
             mView.invalidate();
-            runAfterDraw(() -> assertTrue(mViewRootImpl.getLastPreferredFrameRate() > 0));
+            runAfterDraw(() -> {
+                if (toolkitFrameRateVelocityMappingReadOnly()) {
+                    assertEquals(FRAME_RATE_CATEGORY_LOW,
+                            mViewRootImpl.getLastPreferredFrameRateCategory());
+                    assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f);
+                } else {
+                    assertEquals(FRAME_RATE_CATEGORY_HIGH,
+                            mViewRootImpl.getLastPreferredFrameRateCategory());
+                    assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
+                }
+            });
         });
         waitForAfterDraw();
         sInstrumentation.waitForIdleSync();
-        if (toolkitFrameRateVelocityMappingReadOnly()) {
-            assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_HIGH);
-            assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f);
-        } else {
-            assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_HIGH);
-            assertEquals(mViewRootImpl.getLastPreferredFrameRate(), 0, 0.1);
-        }
     }
 
     /**
@@ -951,7 +966,7 @@
      */
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_insetsAnimation() {
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -977,8 +992,8 @@
         sInstrumentation.waitForIdleSync();
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_HIGH);
+            assertEquals(FRAME_RATE_CATEGORY_HIGH,
+                    viewRootImpl.getLastPreferredFrameRateCategory());
         });
     }
 
@@ -988,7 +1003,7 @@
      */
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_frameRateBoostOnTouch() {
         mView = new View(sContext);
         attachViewToWindow(mView);
@@ -996,9 +1011,9 @@
 
         ViewRootImpl viewRootImpl = mView.getViewRootImpl();
         final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes;
-        assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true);
-        assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
-                attrs.getFrameRateBoostOnTouchEnabled());
+        assertTrue(attrs.getFrameRateBoostOnTouchEnabled());
+        assertEquals(attrs.getFrameRateBoostOnTouchEnabled(),
+                viewRootImpl.getFrameRateBoostOnTouchEnabled());
 
         sInstrumentation.runOnMainSync(() -> {
             attrs.setFrameRateBoostOnTouchEnabled(false);
@@ -1008,9 +1023,9 @@
 
         sInstrumentation.runOnMainSync(() -> {
             final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes;
-            assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false);
-            assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
-                    newAttrs.getFrameRateBoostOnTouchEnabled());
+            assertFalse(newAttrs.getFrameRateBoostOnTouchEnabled());
+            assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(),
+                    viewRootImpl.getFrameRateBoostOnTouchEnabled());
         });
     }
 
@@ -1021,7 +1036,7 @@
      */
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
         final long delay = 200L;
 
@@ -1031,27 +1046,27 @@
         ViewRootImpl viewRootImpl = mView.getViewRootImpl();
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
-            assertEquals(viewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+            assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1);
+            assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    viewRootImpl.getFrameRateCompatibility());
+            assertFalse(viewRootImpl.isFrameRateConflicted());
             viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
-            assertEquals(viewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+            assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1);
+            assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    viewRootImpl.getFrameRateCompatibility());
+            assertFalse(viewRootImpl.isFrameRateConflicted());
             mView.invalidate();
-            assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
-            assertEquals(viewRootImpl.getFrameRateCompatibility(),
-                    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-            assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+            assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1);
+            assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    viewRootImpl.getFrameRateCompatibility());
+            assertFalse(viewRootImpl.isFrameRateConflicted());
         });
 
         Thread.sleep(delay);
-        assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
-        assertEquals(viewRootImpl.getFrameRateCompatibility(),
-                FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
-        assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+        assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1);
+        assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                viewRootImpl.getFrameRateCompatibility());
+        assertFalse(viewRootImpl.isFrameRateConflicted());
     }
 
     /**
@@ -1070,15 +1085,16 @@
         mViewRootImpl = mView.getViewRootImpl();
         waitForFrameRateCategoryToSettle(mView);
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                    mViewRootImpl.getPreferredFrameRateCategory());
 
             mView.setRequestedFrameRate(frameRate);
             mView.invalidate();
             runAfterDraw(() -> {
-                assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                        FRAME_RATE_CATEGORY_NO_PREFERENCE);
-                assertEquals(mViewRootImpl.getLastPreferredFrameRate(), frameRate, 0.1);
+                int expected = toolkitFrameRateDefaultNormalReadOnly()
+                        ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+                assertEquals(expected, mViewRootImpl.getLastPreferredFrameRateCategory());
+                assertEquals(frameRate, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
             });
         });
         waitForAfterDraw();
@@ -1086,17 +1102,17 @@
         // reset the frame rate category counts
         for (int i = 0; i < 5; i++) {
             sInstrumentation.runOnMainSync(() -> {
-                mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+                mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
                 mView.invalidate();
             });
             sInstrumentation.waitForIdleSync();
         }
 
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_LOW));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
     }
@@ -1146,7 +1162,7 @@
         // reset the frame rate category counts
         for (int i = 0; i < 5; i++) {
             sInstrumentation.runOnMainSync(() -> {
-                mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+                mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
                 mView.invalidate();
             });
             sInstrumentation.waitForIdleSync();
@@ -1155,28 +1171,30 @@
         // In transition from frequent update to infrequent update
         Thread.sleep(delay);
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NO_PREFERENCE));
+            runAfterDraw(() -> {
+                assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+                        mViewRootImpl.getLastPreferredFrameRateCategory());
+            });
         });
         waitForAfterDraw();
         Thread.sleep(delay);
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NO_PREFERENCE));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
 
         // Infrequent update
         Thread.sleep(delay);
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NORMAL));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
     }
@@ -1186,7 +1204,7 @@
      */
     @Test
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
         mView = new View(sContext);
         attachViewToWindow(mView);
@@ -1194,9 +1212,9 @@
 
         ViewRootImpl viewRoot = mView.getViewRootImpl();
         final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
-        assertEquals(attrs.isFrameRatePowerSavingsBalanced(), true);
-        assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
-                attrs.isFrameRatePowerSavingsBalanced());
+        assertTrue(attrs.isFrameRatePowerSavingsBalanced());
+        assertEquals(attrs.isFrameRatePowerSavingsBalanced(),
+                viewRoot.isFrameRatePowerSavingsBalanced());
 
         sInstrumentation.runOnMainSync(() -> {
             attrs.setFrameRatePowerSavingsBalanced(false);
@@ -1206,9 +1224,9 @@
 
         sInstrumentation.runOnMainSync(() -> {
             final WindowManager.LayoutParams newAttrs = viewRoot.mWindowAttributes;
-            assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(), false);
-            assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
-                    newAttrs.isFrameRatePowerSavingsBalanced());
+            assertFalse(newAttrs.isFrameRatePowerSavingsBalanced());
+            assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(),
+                    viewRoot.isFrameRatePowerSavingsBalanced());
         });
     }
 
@@ -1243,8 +1261,8 @@
         waitForFrameRateCategoryToSettle(mView);
 
         sInstrumentation.runOnMainSync(() -> {
-            assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+                    mViewRootImpl.getPreferredFrameRateCategory());
             mView.invalidate();
             int expected = toolkitFrameRateDefaultNormalReadOnly()
                     ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
@@ -1259,7 +1277,7 @@
         for (int i = 0; i < 5; i++) {
             Thread.sleep(delay);
             sInstrumentation.runOnMainSync(() -> {
-                mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+                mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
                 mView.invalidate();
             });
             sInstrumentation.waitForIdleSync();
@@ -1267,10 +1285,10 @@
 
         Thread.sleep(delay);
         sInstrumentation.runOnMainSync(() -> {
-            mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+            mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NORMAL));
+            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
     }
@@ -1403,7 +1421,7 @@
             mViewRootImpl.dispatchInputEvent(event);
         });
         sInstrumentation.waitForIdleSync();
-        assertEquals(mKeyReceived, shouldReceiveKey);
+        assertEquals(shouldReceiveKey, mKeyReceived);
     }
 
     private void attachViewToWindow(View view) {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2f2215f..d1d7c14 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,8 +16,6 @@
 
 package android.security;
 
-import android.compat.annotation.UnsupportedAppUsage;
-
 /**
  * This class provides some constants and helper methods related to Android's Keystore service.
  * This class was originally much larger, but its functionality was superseded by other classes.
@@ -30,11 +28,4 @@
 
     // Used for UID field to indicate the calling UID.
     public static final int UID_SELF = -1;
-
-    private static final KeyStore KEY_STORE = new KeyStore();
-
-    @UnsupportedAppUsage
-    public static KeyStore getInstance() {
-        return KEY_STORE;
-    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index b8ac191..0a5a81b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -25,8 +25,8 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
 
-import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET;
-import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET;
+import static androidx.window.extensions.embedding.DividerAttributes.RATIO_SYSTEM_DEFAULT;
+import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_SYSTEM_DEFAULT;
 import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
@@ -64,6 +64,9 @@
 import androidx.annotation.IdRes;
 import androidx.annotation.NonNull;
 import androidx.window.extensions.core.util.function.Consumer;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -76,12 +79,13 @@
  * Manages the rendering and interaction of the divider.
  */
 class DividerPresenter implements View.OnTouchListener {
+    static final float RATIO_EXPANDED_PRIMARY = 1.0f;
+    static final float RATIO_EXPANDED_SECONDARY = 0.0f;
     private static final String WINDOW_NAME = "AE Divider";
     private static final int VEIL_LAYER = 0;
     private static final int DIVIDER_LAYER = 1;
 
     // TODO(b/327067596) Update based on UX guidance.
-    private static final Color DEFAULT_DIVIDER_COLOR = Color.valueOf(Color.BLACK);
     private static final Color DEFAULT_PRIMARY_VEIL_COLOR = Color.valueOf(Color.BLACK);
     private static final Color DEFAULT_SECONDARY_VEIL_COLOR = Color.valueOf(Color.GRAY);
     @VisibleForTesting
@@ -162,54 +166,55 @@
                 return;
             }
 
+            final SplitAttributes splitAttributes = topSplitContainer.getCurrentSplitAttributes();
+            final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+
             // Clean up the decor surface if DividerAttributes is null.
-            final DividerAttributes dividerAttributes =
-                    topSplitContainer.getCurrentSplitAttributes().getDividerAttributes();
             if (dividerAttributes == null) {
                 removeDecorSurfaceAndDivider(wct);
                 return;
             }
 
-            if (topSplitContainer.getCurrentSplitAttributes().getSplitType()
-                    instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
-                // No divider is needed for ExpandContainersSplitType.
-                removeDivider();
-                return;
-            }
+            // At this point, a divider is required.
 
-            // Skip updating when the TFs have not been updated to match the SplitAttributes.
-            if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty()
-                    || topSplitContainer.getSecondaryContainer().getLastRequestedBounds()
-                    .isEmpty()) {
-                return;
-            }
-
+            // Create the decor surface if one is not available yet.
             final SurfaceControl decorSurface = parentInfo.getDecorSurface();
             if (decorSurface == null) {
                 // Clean up when the decor surface is currently unavailable.
                 removeDivider();
                 // Request to create the decor surface
-                createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+                createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer());
                 return;
             }
 
-            // make the top primary container the owner of the decor surface.
-            if (!Objects.equals(mDecorSurfaceOwner,
-                    topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) {
-                createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+            // Update the decor surface owner if needed.
+            boolean isDraggableExpandType =
+                    SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+            final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType
+                    ? topSplitContainer.getSecondaryContainer()
+                    : topSplitContainer.getPrimaryContainer();
+
+            if (!Objects.equals(
+                    mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) {
+                createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer);
             }
+            final boolean isVerticalSplit = isVerticalSplit(topSplitContainer);
+            final boolean isReversedLayout = isReversedLayout(
+                    topSplitContainer.getCurrentSplitAttributes(),
+                    parentInfo.getConfiguration());
 
             updateProperties(
                     new Properties(
                             parentInfo.getConfiguration(),
                             dividerAttributes,
                             decorSurface,
-                            getInitialDividerPosition(topSplitContainer),
-                            isVerticalSplit(topSplitContainer),
-                            isReversedLayout(
-                                    topSplitContainer.getCurrentSplitAttributes(),
-                                    parentInfo.getConfiguration()),
-                            parentInfo.getDisplayId()));
+                            getInitialDividerPosition(
+                                    topSplitContainer, isVerticalSplit, isReversedLayout),
+                            isVerticalSplit,
+                            isReversedLayout,
+                            parentInfo.getDisplayId(),
+                            isDraggableExpandType
+                    ));
         }
     }
 
@@ -242,14 +247,21 @@
      *
      * See {@link TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE}.
      */
-    @GuardedBy("mLock")
-    private void createOrMoveDecorSurface(
+    void createOrMoveDecorSurface(
             @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+        synchronized (mLock) {
+            createOrMoveDecorSurfaceLocked(wct, container);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void createOrMoveDecorSurfaceLocked(
+            @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+        mDecorSurfaceOwner = container.getTaskFragmentToken();
         final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                 OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE)
                 .build();
-        wct.addTaskFragmentOperation(container.getTaskFragmentToken(), operation);
-        mDecorSurfaceOwner = container.getTaskFragmentToken();
+        wct.addTaskFragmentOperation(mDecorSurfaceOwner, operation);
     }
 
     @GuardedBy("mLock")
@@ -274,15 +286,28 @@
     }
 
     @VisibleForTesting
-    static int getInitialDividerPosition(@NonNull SplitContainer splitContainer) {
+    static int getInitialDividerPosition(
+            @NonNull SplitContainer splitContainer,
+            boolean isVerticalSplit,
+            boolean isReversedLayout) {
         final Rect primaryBounds =
                 splitContainer.getPrimaryContainer().getLastRequestedBounds();
         final Rect secondaryBounds =
                 splitContainer.getSecondaryContainer().getLastRequestedBounds();
-        if (isVerticalSplit(splitContainer)) {
-            return Math.min(primaryBounds.right, secondaryBounds.right);
+        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+
+        if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) {
+            // If the container is fully expanded by dragging the divider, we display the divider
+            // on the edge.
+            final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes());
+            final int fullyExpandedPosition = isVerticalSplit
+                    ? primaryBounds.right - dividerWidth
+                    : primaryBounds.bottom - dividerWidth;
+            return isReversedLayout ? fullyExpandedPosition : 0;
         } else {
-            return Math.min(primaryBounds.bottom, secondaryBounds.bottom);
+            return isVerticalSplit
+                    ? Math.min(primaryBounds.right, secondaryBounds.right)
+                    : Math.min(primaryBounds.bottom, secondaryBounds.bottom);
         }
     }
 
@@ -359,14 +384,14 @@
     @VisibleForTesting
     static int getBoundsOffsetForDivider(
             int dividerWidthPx,
-            @NonNull SplitAttributes.SplitType splitType,
+            @NonNull SplitType splitType,
             @SplitPresenter.ContainerPosition int position) {
-        if (splitType instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
-            // No divider is needed for the ExpandContainersSplitType.
+        if (splitType instanceof ExpandContainersSplitType) {
+            // No divider offset is needed for the ExpandContainersSplitType.
             return 0;
         }
         int primaryOffset;
-        if (splitType instanceof final SplitAttributes.SplitType.RatioSplitType splitRatio) {
+        if (splitType instanceof final RatioSplitType splitRatio) {
             // When a divider is present, both containers shrink by an amount proportional to their
             // split ratio and sum to the width of the divider, so that the ending sizing of the
             // containers still maintain the same ratio.
@@ -393,7 +418,8 @@
      * Sanitizes and sets default values in the {@link DividerAttributes}.
      *
      * Unset values will be set with system default values. See
-     * {@link DividerAttributes#WIDTH_UNSET} and {@link DividerAttributes#RATIO_UNSET}.
+     * {@link DividerAttributes#WIDTH_SYSTEM_DEFAULT} and
+     * {@link DividerAttributes#RATIO_SYSTEM_DEFAULT}.
      *
      * @param dividerAttributes input {@link DividerAttributes}
      * @return a {@link DividerAttributes} that has all values properly set.
@@ -405,7 +431,7 @@
             return null;
         }
         int widthDp = dividerAttributes.getWidthDp();
-        if (widthDp == WIDTH_UNSET) {
+        if (widthDp == WIDTH_SYSTEM_DEFAULT) {
             widthDp = DEFAULT_DIVIDER_WIDTH_DP;
         }
 
@@ -416,12 +442,12 @@
         }
 
         float minRatio = dividerAttributes.getPrimaryMinRatio();
-        if (minRatio == RATIO_UNSET) {
+        if (minRatio == RATIO_SYSTEM_DEFAULT) {
             minRatio = DEFAULT_MIN_RATIO;
         }
 
         float maxRatio = dividerAttributes.getPrimaryMaxRatio();
-        if (maxRatio == RATIO_UNSET) {
+        if (maxRatio == RATIO_SYSTEM_DEFAULT) {
             maxRatio = DEFAULT_MAX_RATIO;
         }
 
@@ -438,7 +464,7 @@
             final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
             mDividerPosition = calculateDividerPosition(
                     event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
-                    mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+                    mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition());
             mRenderer.setDividerPosition(mDividerPosition);
             switch (event.getAction()) {
                 case MotionEvent.ACTION_DOWN:
@@ -456,23 +482,27 @@
             }
         }
 
-        // Returns false so that the default button click callback is still triggered, i.e. the
-        // button UI transitions into the "pressed" state.
-        return false;
+        // Returns true to prevent the default button click callback. The button pressed state is
+        // set/unset when starting/finishing dragging.
+        return true;
     }
 
     @GuardedBy("mLock")
     private void onStartDragging() {
         mRenderer.mIsDragging = true;
+        mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         mRenderer.updateSurface(t);
         mRenderer.showVeils(t);
-        final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
         mCallbackExecutor.execute(() -> {
             mDragEventCallback.onStartDragging(
-                    wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, true /* boosted */, t));
+                    wct -> {
+                        synchronized (mLock) {
+                            setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, true /* boosted */, t);
+                        }
+                    });
         });
     }
 
@@ -485,18 +515,62 @@
 
     @GuardedBy("mLock")
     private void onFinishDragging() {
+        mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
+        mRenderer.setDividerPosition(mDividerPosition);
+
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         mRenderer.updateSurface(t);
         mRenderer.hideVeils(t);
-        final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
+        // mDecorSurfaceOwner may change between here and when the callback is executed,
+        // e.g. when the decor surface owner becomes the secondary container when it is expanded to
+        // fullscreen.
         mCallbackExecutor.execute(() -> {
             mDragEventCallback.onFinishDragging(
                     mTaskId,
-                    wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, false /* boosted */, t));
+                    wct -> {
+                        synchronized (mLock) {
+                            setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, false /* boosted */, t);
+                        }
+                    });
         });
         mRenderer.mIsDragging = false;
+        mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+    }
+
+    /**
+     * Returns the divider position adjusted for the min max ratio and fullscreen expansion.
+     *
+     * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below
+     * {@link DividerAttributes#getPrimaryMinRatio()} and
+     * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will
+     * choose a snap algorithm to adjust the ending position to either fully expand one container or
+     * move the divider back to the specified min/max ratio.
+     *
+     * TODO(b/327067596) implement snap algorithm
+     *
+     * The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0
+     * for expanded right (bottom) container, or task width (height) minus the divider width for
+     * expanded left (top) container.
+     */
+    @GuardedBy("mLock")
+    private int adjustDividerPositionForSnapPoints(int dividerPosition) {
+        final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+        final int minPosition = calculateMinPosition();
+        final int maxPosition = calculateMaxPosition();
+        final int fullyExpandedPosition = mProperties.mIsVerticalSplit
+                ? taskBounds.right - mRenderer.mDividerWidthPx
+                : taskBounds.bottom - mRenderer.mDividerWidthPx;
+        if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
+            if (dividerPosition < minPosition) {
+                return 0;
+            }
+            if (dividerPosition > maxPosition) {
+                return fullyExpandedPosition;
+            }
+        }
+        return Math.clamp(dividerPosition, minPosition, maxPosition);
     }
 
     private static void setDecorSurfaceBoosted(
@@ -520,7 +594,7 @@
     @VisibleForTesting
     static int calculateDividerPosition(@NonNull MotionEvent event, @NonNull Rect taskBounds,
             int dividerWidthPx, @NonNull DividerAttributes dividerAttributes,
-            boolean isVerticalSplit, boolean isReversedLayout) {
+            boolean isVerticalSplit, int minPosition, int maxPosition) {
         // The touch event is in display space. Converting it into the task window space.
         final int touchPositionInTaskSpace = isVerticalSplit
                 ? (int) (event.getRawX()) - taskBounds.left
@@ -530,15 +604,31 @@
         // position is offset by half of the divider width.
         int dividerPosition = touchPositionInTaskSpace - dividerWidthPx / 2;
 
-        // Limit the divider position to the min and max ratios set in DividerAttributes.
-        // TODO(b/327536303) Handle when the divider is dragged to the edge.
-        dividerPosition = Math.max(dividerPosition, calculateMinPosition(
-                taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
-        dividerPosition = Math.min(dividerPosition, calculateMaxPosition(
-                taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
+        // If dragging to fullscreen is not allowed, limit the divider position to the min and max
+        // ratios set in DividerAttributes. Otherwise, dragging beyond the min and max ratios is
+        // temporarily allowed and the final ratio will be adjusted in onFinishDragging.
+        if (!isDraggingToFullscreenAllowed(dividerAttributes)) {
+            dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+        }
         return dividerPosition;
     }
 
+    @GuardedBy("mLock")
+    private int calculateMinPosition() {
+        return calculateMinPosition(
+                mProperties.mConfiguration.windowConfiguration.getBounds(),
+                mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+                mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+    }
+
+    @GuardedBy("mLock")
+    private int calculateMaxPosition() {
+        return calculateMaxPosition(
+                mProperties.mConfiguration.windowConfiguration.getBounds(),
+                mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+                mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+    }
+
     /** Calculates the min position of the divider that the user is allowed to drag to. */
     @VisibleForTesting
     static int calculateMinPosition(@NonNull Rect taskBounds, int dividerWidthPx,
@@ -581,13 +671,24 @@
                     mProperties.mConfiguration.windowConfiguration.getBounds(),
                     mRenderer.mDividerWidthPx,
                     mProperties.mIsVerticalSplit,
-                    mProperties.mIsReversedLayout);
+                    mProperties.mIsReversedLayout,
+                    calculateMinPosition(),
+                    calculateMaxPosition(),
+                    isDraggingToFullscreenAllowed(mProperties.mDividerAttributes));
         }
     }
 
+    private static boolean isDraggingToFullscreenAllowed(
+            @NonNull DividerAttributes dividerAttributes) {
+        // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
+        // updated.
+        return true;
+    }
+
     /**
      * Returns the new split ratio of the {@link SplitContainer} based on the current divider
      * position.
+     *
      * @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio.
      * @param dividerPosition the divider position. See {@link #mDividerPosition}.
      * @param taskBounds the task bounds
@@ -599,7 +700,9 @@
      *                         bottom-to-top. If {@code false}, the split is not reversed, i.e.
      *                         left-to-right or top-to-bottom. See
      *                         {@link SplitAttributesHelper#isReversedLayout}
-     * @return the computed split ratio of the primary container.
+     * @return the computed split ratio of the primary container. If the primary container is fully
+     * expanded, {@link #RATIO_EXPANDED_PRIMARY} is returned. If the secondary container is fully
+     * expanded, {@link #RATIO_EXPANDED_SECONDARY} is returned.
      */
     @VisibleForTesting
     static float calculateNewSplitRatio(
@@ -608,15 +711,33 @@
             @NonNull Rect taskBounds,
             int dividerWidthPx,
             boolean isVerticalSplit,
-            boolean isReversedLayout) {
+            boolean isReversedLayout,
+            int minPosition,
+            int maxPosition,
+            boolean isDraggingToFullscreenAllowed) {
+
+        // Handle the fully expanded cases.
+        if (isDraggingToFullscreenAllowed) {
+            // The divider position is already adjusted by the snap algorithm in onFinishDragging.
+            // If the divider position is not in the range [minPosition, maxPosition], then one of
+            // the containers is fully expanded.
+            if (dividerPosition < minPosition) {
+                return isReversedLayout ? RATIO_EXPANDED_PRIMARY : RATIO_EXPANDED_SECONDARY;
+            }
+            if (dividerPosition > maxPosition) {
+                return isReversedLayout ? RATIO_EXPANDED_SECONDARY : RATIO_EXPANDED_PRIMARY;
+            }
+        } else {
+            dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+        }
+
+        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
+        final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
         final int usableSize = isVerticalSplit
                 ? taskBounds.width() - dividerWidthPx
                 : taskBounds.height() - dividerWidthPx;
 
-        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
-        final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
-
-        float newRatio;
+        final float newRatio;
         if (isVerticalSplit) {
             final int newPrimaryWidth = isReversedLayout
                     ? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx))
@@ -677,6 +798,7 @@
 
         private final int mDisplayId;
         private final boolean mIsReversedLayout;
+        private final boolean mIsDraggableExpandType;
 
         @VisibleForTesting
         Properties(
@@ -686,7 +808,8 @@
                 int initialDividerPosition,
                 boolean isVerticalSplit,
                 boolean isReversedLayout,
-                int displayId) {
+                int displayId,
+                boolean isDraggableExpandType) {
             mConfiguration = configuration;
             mDividerAttributes = dividerAttributes;
             mDecorSurface = decorSurface;
@@ -694,6 +817,7 @@
             mIsVerticalSplit = isVerticalSplit;
             mIsReversedLayout = isReversedLayout;
             mDisplayId = displayId;
+            mIsDraggableExpandType = isDraggableExpandType;
         }
 
         /**
@@ -714,7 +838,8 @@
                     && a.mInitialDividerPosition == b.mInitialDividerPosition
                     && a.mIsVerticalSplit == b.mIsVerticalSplit
                     && a.mDisplayId == b.mDisplayId
-                    && a.mIsReversedLayout == b.mIsReversedLayout;
+                    && a.mIsReversedLayout == b.mIsReversedLayout
+                    && a.mIsDraggableExpandType == b.mIsDraggableExpandType;
         }
 
         private static boolean areSameSurfaces(
@@ -761,6 +886,7 @@
         private SurfaceControl mSecondaryVeil;
         private boolean mIsDragging;
         private int mDividerPosition;
+        private View mDragHandle;
 
         private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
             mProperties = properties;
@@ -857,6 +983,7 @@
                             PixelFormat.TRANSLUCENT);
             lp.setTitle(WINDOW_NAME);
             mViewHost.setView(mDividerLayout, lp);
+            mViewHost.relayout(lp);
         }
 
         /**
@@ -867,7 +994,12 @@
          */
         private void updateDivider(@NonNull SurfaceControl.Transaction t) {
             mDividerLayout.removeAllViews();
-            mDividerLayout.setBackgroundColor(DEFAULT_DIVIDER_COLOR.toArgb());
+            if (mProperties.mIsDraggableExpandType) {
+                // If a container is fully expanded, the divider overlays on the expanded container.
+                mDividerLayout.setBackgroundColor(Color.TRANSPARENT);
+            } else {
+                mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
+            }
             if (mProperties.mDividerAttributes.getDividerType()
                     == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
                 createVeils();
@@ -916,6 +1048,7 @@
             }
 
             button.setOnTouchListener(mListener);
+            mDragHandle = button;
             mDividerLayout.addView(button);
         }
 
@@ -928,7 +1061,7 @@
                     .setHidden(!visible)
                     .setCallsite("DividerManager.createChildSurface")
                     .setBufferSize(bounds.width(), bounds.height())
-                    .setColorLayer()
+                    .setEffectLayer()
                     .build();
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
index 042a68a6..4541a84 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
@@ -20,6 +20,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
 
 /** Helper functions for {@link SplitAttributes} */
 class SplitAttributesHelper {
@@ -43,4 +44,17 @@
                         "Invalid layout direction:" + splitAttributes.getLayoutDirection());
         }
     }
+
+    /**
+     * Returns whether the {@link SplitAttributes} is an {@link ExpandContainersSplitType} and it
+     * should show a draggable handle that allows the user to drag and restore it into a split.
+     * This state is a result of user dragging the divider to fully expand the secondary container.
+     */
+    static boolean isDraggableExpandType(@NonNull SplitAttributes splitAttributes) {
+        final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+        return splitAttributes.getSplitType() instanceof ExpandContainersSplitType
+                && dividerAttributes != null
+                && dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE;
+
+    }
 }
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 b9b86f0..0f04321 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -115,6 +116,11 @@
     static final boolean ENABLE_SHELL_TRANSITIONS =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
 
+    // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
+    //  association. It's not set in WM Extensions nor Wm Jetpack library currently.
+    private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
+            "androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity";
+
     @VisibleForTesting
     @GuardedBy("mLock")
     final SplitPresenter mPresenter;
@@ -1405,9 +1411,27 @@
         launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
     }
 
+    @GuardedBy("mLock")
+    private void onActivityPaused(@NonNull WindowContainerTransaction wct,
+                                  @NonNull Activity activity) {
+        // Checks if there's any finishing activity in paused state associate with an overlay
+        // container. #OnActivityPostDestroyed is a very late signal, which is called after activity
+        // is not visible and the next activity shows on screen.
+        if (!activity.isFinishing()) {
+            // onPaused is triggered without finishing. Early return.
+            return;
+        }
+        // Check if we should dismiss the overlay container with this finishing activity.
+        final IBinder activityToken = activity.getActivityToken();
+        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+            mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken);
+        }
+        updateCallbackIfNecessary();
+    }
+
     @VisibleForTesting
     @GuardedBy("mLock")
-    void onActivityDestroyed(@NonNull Activity activity) {
+    void onActivityDestroyed(@NonNull WindowContainerTransaction wct, @NonNull Activity activity) {
         if (!activity.isFinishing()) {
             // onDestroyed is triggered without finishing. This happens when the activity is
             // relaunched. In this case, we don't want to cleanup the record.
@@ -1417,7 +1441,7 @@
         // organizer.
         final IBinder activityToken = activity.getActivityToken();
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
+            mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken);
         }
         // We didn't trigger the callback if there were any pending appeared activities, so check
         // again after the pending is removed.
@@ -1597,7 +1621,8 @@
             @Nullable Activity launchingActivity) {
         return createEmptyContainer(wct, intent, taskId,
                 new ActivityStackAttributes.Builder().build(), launchingActivity,
-                null /* overlayTag */, null /* launchOptions */);
+                null /* overlayTag */, null /* launchOptions */,
+                false /* shouldAssociateWithLaunchingActivity */);
     }
 
     /**
@@ -1612,7 +1637,7 @@
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @NonNull ActivityStackAttributes activityStackAttributes,
             @Nullable Activity launchingActivity, @Nullable String overlayTag,
-            @Nullable Bundle launchOptions) {
+            @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
         // We need an activity in the organizer process in the same Task to use as the owner
         // activity, as well as to get the Task window info.
         final Activity activityInTask;
@@ -1630,7 +1655,7 @@
         }
         final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
                 intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
-                launchOptions);
+                launchOptions, associateLaunchingActivity);
         final IBinder taskFragmentToken = container.getTaskFragmentToken();
         // Note that taskContainer will not exist before calling #newContainer if the container
         // is the first embedded TF in the task.
@@ -1722,7 +1747,7 @@
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
                 activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
-                null /* launchOptions */);
+                null /* launchOptions */, false /* associateLaunchingActivity */);
     }
 
     @GuardedBy("mLock")
@@ -1730,7 +1755,7 @@
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
                 activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
-                null /* launchOptions */);
+                null /* launchOptions */, false /* associateLaunchingActivity */);
     }
 
     @GuardedBy("mLock")
@@ -1739,7 +1764,7 @@
             @NonNull TaskFragmentContainer pairedPrimaryContainer) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
                 activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
-                null /* launchOptions */);
+                null /* launchOptions */, false /* associateLaunchingActivity */);
     }
 
     /**
@@ -1751,19 +1776,21 @@
      * @param activityInTask          activity in the same Task so that we can get the Task bounds
      *                                if needed.
      * @param taskId                  parent Task of the new TaskFragment.
-     * @param pairedPrimaryContainer  the paired primary {@link TaskFragmentContainer}. When it is
+     * @param pairedContainer  the paired primary {@link TaskFragmentContainer}. When it is
      *                                set, the new container will be added right above it.
      * @param overlayTag              The tag for the new created overlay container. It must be
      *                                needed if {@code isOverlay} is {@code true}. Otherwise,
      *                                it should be {@code null}.
      * @param launchOptions           The launch options bundle to create a container. Must be
      *                                specified for overlay container.
+     * @param associateLaunchingActivity {@code true} to indicate this overlay container
+     *                                   should associate with launching activity.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
-            @Nullable Bundle launchOptions) {
+            @Nullable TaskFragmentContainer pairedContainer, @Nullable String overlayTag,
+            @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
         if (activityInTask == null) {
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
@@ -1773,8 +1800,8 @@
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
-                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
-                launchOptions);
+                pendingAppearedIntent, taskContainer, this, pairedContainer, overlayTag,
+                launchOptions, associateLaunchingActivity ? activityInTask : null);
         return container;
     }
 
@@ -2141,6 +2168,10 @@
             return false;
         }
 
+        if (container != null && container.getTaskContainer().isPlaceholderRuleSuppressed()) {
+            return false;
+        }
+
         final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
                 placeholderRule.getPlaceholderIntent());
@@ -2236,6 +2267,9 @@
         if (SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
+        if (SplitPresenter.shouldShowPlaceholderWhenExpanded(splitAttributes)) {
+            return false;
+        }
 
         mTransactionManager.getCurrentTransactionRecord()
                 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
@@ -2617,6 +2651,8 @@
         final List<TaskFragmentContainer> overlayContainers =
                 getAllOverlayTaskFragmentContainers();
         final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
+        final boolean associateLaunchingActivity = options
+                .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true);
 
         // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
         // specified by Intent, expand the overlay container to fill the parent task instead.
@@ -2646,6 +2682,11 @@
                 }
                 if (overlayTag.equals(overlayContainer.getOverlayTag())
                         && taskId != overlayContainer.getTaskId()) {
+                    Log.w(TAG, "The overlay container with tag:"
+                            + overlayContainer.getOverlayTag() + " is dismissed because"
+                            + " there's an existing overlay container with the same tag but"
+                            + " different task ID:" + overlayContainer.getTaskId() + ". "
+                            + "The new associated activity is " + launchActivity);
                     // If there's an overlay container with same tag in a different task,
                     // dismiss the overlay container since the tag must be unique per process.
                     mPresenter.cleanupContainer(wct, overlayContainer,
@@ -2653,19 +2694,46 @@
                 }
                 if (overlayTag.equals(overlayContainer.getOverlayTag())
                         && taskId == overlayContainer.getTaskId()) {
-                    mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
-                            getMinDimensions(intent));
-                    // We can just return the updated overlay container and don't need to
-                    // check other condition since we only have one OverlayCreateParams, and
-                    // if the tag and task are matched, it's impossible to match another task
-                    // or tag since tags and tasks are all unique.
-                    return overlayContainer;
+                    if (associateLaunchingActivity && !launchActivity.getActivityToken()
+                            .equals(overlayContainer.getAssociatedActivityToken())) {
+                        Log.w(TAG, "The overlay container with tag:"
+                                + overlayContainer.getOverlayTag() + " is dismissed because"
+                                + " there's an existing overlay container with the same tag but"
+                                + " different associated launching activity. The new associated"
+                                + " activity is " + launchActivity);
+                        // The associated activity must be the same, or it will be dismissed.
+                        mPresenter.cleanupContainer(wct, overlayContainer,
+                                false /* shouldFinishDependant */);
+                    } else if (!associateLaunchingActivity
+                            && overlayContainer.isAssociatedWithActivity()) {
+                        Log.w(TAG, "The overlay container with tag:"
+                                + overlayContainer.getOverlayTag() + " is dismissed because"
+                                + " there's an existing overlay container with the same tag but"
+                                + " different associated launching activity. The overlay container"
+                                + " doesn't associate with any activity.");
+                        // Dismiss the overlay container since it has been associated with an
+                        // activity.
+                        mPresenter.cleanupContainer(wct, overlayContainer,
+                                false /* shouldFinishDependant */);
+                    } else {
+                        // Just update the overlay container if
+                        // - should associate with an activity and associated activity matches
+                        // - should not associate with an activity and the overlay container
+                        //     don't have an associated activity
+                        mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+                                getMinDimensions(intent));
+                        // We can just return the updated overlay container and don't need to
+                        // check other condition since we only have one OverlayCreateParams, and
+                        // if the tag and task are matched, it's impossible to match another task
+                        // or tag since tags and tasks are all unique.
+                        return overlayContainer;
+                    }
                 }
             }
         }
         // Launch the overlay container to the task with taskId.
         return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
-                options);
+                options, associateLaunchingActivity);
     }
 
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2754,6 +2822,23 @@
         }
 
         @Override
+        public void onActivityPostPaused(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
+            synchronized (mLock) {
+                final TransactionRecord transactionRecord = mTransactionManager
+                        .startNewTransaction();
+                transactionRecord.setOriginType(TRANSIT_CLOSE);
+                SplitController.this.onActivityPaused(
+                        transactionRecord.getTransaction(), activity);
+                transactionRecord.apply(false /* shouldApplyIndependently */);
+            }
+        }
+
+        @Override
         public void onActivityPostDestroyed(@NonNull Activity activity) {
             if (activity.isChild()) {
                 // Skip Activity that is child of another Activity (ActivityGroup) because it's
@@ -2761,7 +2846,12 @@
                 return;
             }
             synchronized (mLock) {
-                SplitController.this.onActivityDestroyed(activity);
+                final TransactionRecord transactionRecord = mTransactionManager
+                        .startNewTransaction();
+                transactionRecord.setOriginType(TRANSIT_CLOSE);
+                SplitController.this.onActivityDestroyed(
+                        transactionRecord.getTransaction(), activity);
+                transactionRecord.apply(false /* shouldApplyIndependently */);
             }
         }
     }
@@ -3111,7 +3201,12 @@
             if (taskContainer != null) {
                 final DividerPresenter dividerPresenter =
                         mDividerPresenters.get(taskContainer.getTaskId());
-                taskContainer.updateTopSplitContainerForDivider(dividerPresenter);
+                final List<TaskFragmentContainer> containersToFinish = new ArrayList<>();
+                taskContainer.updateTopSplitContainerForDivider(
+                        dividerPresenter, containersToFinish);
+                for (final TaskFragmentContainer container : containersToFinish) {
+                    mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+                }
                 updateContainersInTask(wct, taskContainer);
             }
             action.accept(wct);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0d31266..b56f671 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -565,7 +565,7 @@
 
     @Override
     void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
-            @Nullable IBinder secondary) {
+                                  @Nullable IBinder secondary) {
         final TaskFragmentContainer container = mController.getContainer(primary);
         if (container == null) {
             throw new IllegalStateException("setCompanionTaskFragment on TaskFragment that is"
@@ -590,7 +590,12 @@
         final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
                 taskBounds);
         final boolean isFillParent = relativeBounds.isEmpty();
-        final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+        // Note that we only set isolated navigation for overlay container without activity
+        // association. Activity will be launched to an expanded container on top of the overlay
+        // if the overlay is associated with an activity. Thus, an overlay with activity association
+        // will never be isolated navigated.
+        final boolean isIsolatedNavigated = container.isOverlay()
+                && !container.isAssociatedWithActivity() && !isFillParent;
         final boolean dimOnTask = !isFillParent
                 && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
                 && Flags.fullscreenDimFlag();
@@ -716,6 +721,12 @@
         return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
     }
 
+    static boolean shouldShowPlaceholderWhenExpanded(@NonNull SplitAttributes splitAttributes) {
+        // The placeholder should be kept if the expand split type is a result of user dragging
+        // the divider.
+        return SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+    }
+
     @NonNull
     SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
             @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
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 a215bdf..3dd96c4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -22,6 +22,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_PRIMARY;
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_SECONDARY;
+
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.WindowConfiguration;
@@ -40,6 +43,9 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -89,6 +95,25 @@
     final Set<IBinder> mFinishedContainer = new ArraySet<>();
 
     /**
+     * The {@link RatioSplitType} that will be applied to newly added containers. This is to ensure
+     * the required UX that, after user dragging the divider, the split ratio is persistent after
+     * launching a new activity into a new TaskFragment in the same Task.
+     */
+    private RatioSplitType mOverrideSplitType;
+
+    /**
+     * If {@code true}, suppress the placeholder rules in the {@link TaskContainer}.
+     * <p>
+     * This is used in case the user drags the divider to fully expand the primary container and
+     * dismiss the secondary container while a {@link SplitPlaceholderRule} is used. Without this
+     * flag, after dismissing the secondary container, a placeholder will be launched again.
+     * <p>
+     * This flag is set true when the primary container is fully expanded and cleared when a new
+     * split is added to the {@link TaskContainer}.
+     */
+    private boolean mPlaceholderRuleSuppressed;
+
+    /**
      * The {@link TaskContainer} constructor
      *
      * @param taskId         The ID of the Task, which must match {@link Activity#getTaskId()} with
@@ -211,10 +236,19 @@
         return mContainers.isEmpty() && mFinishedContainer.isEmpty();
     }
 
-    /** Called when the activity is destroyed. */
-    void onActivityDestroyed(@NonNull IBinder activityToken) {
+    /** Called when the activity {@link Activity#isFinishing()} and paused. */
+    void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
+                                   @NonNull IBinder activityToken) {
         for (TaskFragmentContainer container : mContainers) {
-            container.onActivityDestroyed(activityToken);
+            container.onFinishingActivityPaused(wct, activityToken);
+        }
+    }
+
+    /** Called when the activity is destroyed. */
+    void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
+                             @NonNull IBinder activityToken) {
+        for (TaskFragmentContainer container : mContainers) {
+            container.onActivityDestroyed(wct, activityToken);
         }
     }
 
@@ -313,6 +347,11 @@
     }
 
     void addSplitContainer(@NonNull SplitContainer splitContainer) {
+        // Reset the placeholder rule suppression when a new split container is added.
+        mPlaceholderRuleSuppressed = false;
+
+        applyOverrideSplitTypeIfNeeded(splitContainer);
+
         if (splitContainer instanceof SplitPinContainer) {
             mSplitPinContainer = (SplitPinContainer) splitContainer;
             mSplitContainers.add(splitContainer);
@@ -327,6 +366,39 @@
         }
     }
 
+    boolean isPlaceholderRuleSuppressed() {
+        return mPlaceholderRuleSuppressed;
+    }
+
+    // If there is an override SplitType due to user dragging the divider, the split ratio should
+    // be applied to newly added SplitContainers.
+    private void applyOverrideSplitTypeIfNeeded(@NonNull SplitContainer splitContainer) {
+        if (mOverrideSplitType == null) {
+            return;
+        }
+        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+        final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+        if (!(splitAttributes.getSplitType() instanceof RatioSplitType)) {
+            // Skip if the original split type is not a ratio type.
+            return;
+        }
+        if (dividerAttributes == null
+                || dividerAttributes.getDividerType() != DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+            // Skip if the split does not have a draggable divider.
+            return;
+        }
+        updateDefaultSplitAttributes(splitContainer, mOverrideSplitType);
+    }
+
+    private static void updateDefaultSplitAttributes(
+            @NonNull SplitContainer splitContainer, @NonNull SplitType overrideSplitType) {
+        splitContainer.updateDefaultSplitAttributes(
+                new SplitAttributes.Builder(splitContainer.getDefaultSplitAttributes())
+                        .setSplitType(overrideSplitType)
+                        .build()
+        );
+    }
+
     void removeSplitContainers(@NonNull List<SplitContainer> containers) {
         mSplitContainers.removeAll(containers);
     }
@@ -398,18 +470,47 @@
         return mContainers;
     }
 
-    void updateTopSplitContainerForDivider(@NonNull DividerPresenter dividerPresenter) {
+    void updateTopSplitContainerForDivider(
+            @NonNull DividerPresenter dividerPresenter,
+            @NonNull List<TaskFragmentContainer> outContainersToFinish) {
         final SplitContainer topSplitContainer = getTopNonFinishingSplitContainer();
         if (topSplitContainer == null) {
             return;
         }
-
+        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
         final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer);
-        topSplitContainer.updateDefaultSplitAttributes(
-                new SplitAttributes.Builder(topSplitContainer.getDefaultSplitAttributes())
-                        .setSplitType(new SplitAttributes.SplitType.RatioSplitType(newRatio))
-                        .build()
-        );
+
+        // If the primary container is fully expanded, we should finish all the associated
+        // secondary containers.
+        if (newRatio == RATIO_EXPANDED_PRIMARY) {
+            for (final SplitContainer splitContainer : mSplitContainers) {
+                if (primaryContainer == splitContainer.getPrimaryContainer()) {
+                    outContainersToFinish.add(splitContainer.getSecondaryContainer());
+                }
+            }
+
+            // Temporarily suppress the placeholder rule in the TaskContainer. This will be restored
+            // if a new split is added into the TaskContainer.
+            mPlaceholderRuleSuppressed = true;
+
+            mOverrideSplitType = null;
+            return;
+        }
+
+        final SplitType newSplitType;
+        if (newRatio == RATIO_EXPANDED_SECONDARY) {
+            newSplitType = new ExpandContainersSplitType();
+            // We do not want to apply ExpandContainersSplitType to new split containers.
+            mOverrideSplitType = null;
+        } else {
+            // We save the override RatioSplitType and apply to new split containers.
+            newSplitType = mOverrideSplitType = new RatioSplitType(newRatio);
+        }
+        for (final SplitContainer splitContainer : mSplitContainers) {
+            if (primaryContainer == splitContainer.getPrimaryContainer()) {
+                updateDefaultSplitAttributes(splitContainer, newSplitType);
+            }
+        }
     }
 
     @Nullable
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e20a3e0..5dbb016 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -113,6 +113,18 @@
     @NonNull
     private final Bundle mLaunchOptions = new Bundle();
 
+    /**
+     * The associated {@link Activity#getActivityToken()} of the overlay container.
+     * Must be {@code null} for non-overlay container.
+     * <p>
+     * If an overlay container is associated with an activity, this overlay container will be
+     * dismissed when the associated activity is destroyed. If the overlay container is visible,
+     * activity will be launched on top of the overlay container and expanded to fill the parent
+     * container.
+     */
+    @Nullable
+    private final IBinder mAssociatedActivityToken;
+
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
 
@@ -178,7 +190,7 @@
 
     /**
      * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
-     * TaskFragmentContainer, String, Bundle)
+     * TaskFragmentContainer, String, Bundle, Activity)
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
                           @Nullable Intent pendingAppearedIntent,
@@ -187,7 +199,7 @@
                           @Nullable TaskFragmentContainer pairedPrimaryContainer) {
         this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
                 controller, pairedPrimaryContainer, null /* overlayTag */,
-                null /* launchOptions */);
+                null /* launchOptions */, null /* associatedActivity */);
     }
 
     /**
@@ -197,12 +209,14 @@
      * @param overlayTag                Sets to indicate this taskFragment is an overlay container
      * @param launchOptions             The launch options to create this container. Must not be
      *                                  {@code null} for an overlay container
+     * @param associatedActivity        the associated activity of the overlay container. Must be
+     *                                  {@code null} for a non-overlay container.
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
             @NonNull SplitController controller,
             @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
-            @Nullable Bundle launchOptions) {
+            @Nullable Bundle launchOptions, @Nullable Activity associatedActivity) {
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
@@ -214,7 +228,13 @@
         mOverlayTag = overlayTag;
         if (overlayTag != null) {
             Objects.requireNonNull(launchOptions);
+        } else if (associatedActivity != null) {
+            throw new IllegalArgumentException("Associated activity must be null for "
+                    + "non-overlay activity.");
         }
+        mAssociatedActivityToken = associatedActivity != null
+                ? associatedActivity.getActivityToken() : null;
+
         if (launchOptions != null) {
             mLaunchOptions.putAll(launchOptions);
         }
@@ -420,14 +440,38 @@
         }
     }
 
+    /** Called when the activity {@link Activity#isFinishing()} and paused. */
+    void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
+                                   @NonNull IBinder activityToken) {
+        finishSelfWithActivityIfNeeded(wct, activityToken);
+    }
+
     /** Called when the activity is destroyed. */
-    void onActivityDestroyed(@NonNull IBinder activityToken) {
+    void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
+                             @NonNull IBinder activityToken) {
         removePendingAppearedActivity(activityToken);
         if (mInfo != null) {
             // Remove the activity now because there can be a delay before the server callback.
             mInfo.getActivities().remove(activityToken);
         }
         mActivitiesToFinishOnExit.remove(activityToken);
+        finishSelfWithActivityIfNeeded(wct, activityToken);
+    }
+
+    @VisibleForTesting
+    void finishSelfWithActivityIfNeeded(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder activityToken) {
+        if (mIsFinished) {
+            return;
+        }
+        // Early return if this container is not an overlay with activity association.
+        if (!isOverlay() || !isAssociatedWithActivity()) {
+            return;
+        }
+        if (mAssociatedActivityToken == activityToken) {
+            // If the associated activity is destroyed, also finish this overlay container.
+            mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */);
+        }
     }
 
     @Nullable
@@ -961,6 +1005,24 @@
         return mLaunchOptions;
     }
 
+    /**
+     * Returns the associated Activity token of this overlay container. It must be {@code null}
+     * for non-overlay container.
+     * <p>
+     * If an overlay container is associated with an activity, this overlay container will be
+     * dismissed when the associated activity is destroyed. If the overlay container is visible,
+     * activity will be launched on top of the overlay container and expanded to fill the parent
+     * container.
+     */
+    @Nullable
+    IBinder getAssociatedActivityToken() {
+        return mAssociatedActivityToken;
+    }
+
+    boolean isAssociatedWithActivity() {
+        return mAssociatedActivityToken != null;
+    }
+
     @Override
     public String toString() {
         return toString(true /* includeContainersToFinishOnExit */);
@@ -980,6 +1042,7 @@
                 + " runningActivityCount=" + getRunningActivityCount()
                 + " isFinished=" + mIsFinished
                 + " overlayTag=" + mOverlayTag
+                + " associatedActivity" + mAssociatedActivityToken
                 + " lastRequestedBounds=" + mLastRequestedBounds
                 + " pendingAppearedActivities=" + mPendingAppearedActivities
                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 47d01da..de0171d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -144,10 +144,12 @@
                 new Configuration(),
                 DEFAULT_DIVIDER_ATTRIBUTES,
                 mSurfaceControl,
-                getInitialDividerPosition(mSplitContainer),
+                getInitialDividerPosition(
+                        mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */),
                 true /* isVerticalSplit */,
                 false /* isReversedLayout */,
-                Display.DEFAULT_DISPLAY);
+                Display.DEFAULT_DISPLAY,
+                false /* isDraggableExpandType */);
 
         mDividerPresenter = new DividerPresenter(
                 MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
@@ -348,7 +350,8 @@
                         dividerWidthPx,
                         dividerAttributes,
                         true /* isVerticalSplit */,
-                        false /* isReversedLayout */));
+                        0 /* minPosition */,
+                        900 /* maxPosition */));
 
         // Top-to-bottom split
         when(event.getRawY()).thenReturn(1000f); // Touch event is in display space
@@ -361,7 +364,8 @@
                         dividerWidthPx,
                         dividerAttributes,
                         false /* isVerticalSplit */,
-                        false /* isReversedLayout */));
+                        0 /* minPosition */,
+                        900 /* maxPosition */));
     }
 
     @Test
@@ -453,7 +457,6 @@
         final Rect primaryBounds = new Rect(0, 0, 500, 2000);
         final Rect secondaryBounds = new Rect(600, 0, 1100, 2000);
         final int dividerWidthPx = 100;
-        final int dividerPosition = 300;
 
         final TaskFragmentContainer mockPrimaryContainer =
                 createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -462,6 +465,8 @@
         when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
         when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
 
+        // Test the normal case
+        int dividerPosition = 300;
         assertEquals(
                 0.3f, // Primary is 300px after dragging.
                 DividerPresenter.calculateNewSplitRatio(
@@ -470,7 +475,43 @@
                         taskBounds,
                         dividerWidthPx,
                         true /* isVerticalSplit */,
-                        false /* isReversedLayout */),
+                        false /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        false /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is allowed and divider is dragged to the edge
+        dividerPosition = 0;
+        assertEquals(
+                DividerPresenter.RATIO_EXPANDED_SECONDARY,
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        true /* isVerticalSplit */,
+                        false /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        true /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+        // edge.
+        dividerPosition = 0;
+        assertEquals(
+                0.2f, // Adjusted to the minPosition 200
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        true /* isVerticalSplit */,
+                        false /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        false /* isDraggingToFullscreenAllowed */),
                 0.0001 /* delta */);
     }
 
@@ -482,7 +523,6 @@
         final Rect primaryBounds = new Rect(0, 0, 2000, 1100);
         final Rect secondaryBounds = new Rect(0, 0, 2000, 500);
         final int dividerWidthPx = 100;
-        final int dividerPosition = 300;
 
         final TaskFragmentContainer mockPrimaryContainer =
                 createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -491,6 +531,8 @@
         when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
         when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
 
+        // Test the normal case
+        int dividerPosition = 300;
         assertEquals(
                 // After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100].
                 0.7f,
@@ -500,7 +542,46 @@
                         taskBounds,
                         dividerWidthPx,
                         false /* isVerticalSplit */,
-                        true /* isReversedLayout */),
+                        true /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        true /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+        // edge.
+        dividerPosition = 0;
+        assertEquals(
+                // The primary (bottom) container is expanded
+                DividerPresenter.RATIO_EXPANDED_PRIMARY,
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        false /* isVerticalSplit */,
+                        true /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        true /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+        // edge.
+        dividerPosition = 0;
+        assertEquals(
+                // Adjusted to minPosition 200, so the primary (bottom) container is 800.
+                0.8f,
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        false /* isVerticalSplit */,
+                        true /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        false /* isDraggingToFullscreenAllowed */),
                 0.0001 /* delta */);
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 28fbadb..dcdbe59 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -227,14 +228,15 @@
     }
 
     @Test
-    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+    public void testCreateOrUpdateOverlay_sameTagTaskAndActivity_updateOverlay() {
         createExistingOverlayContainers();
 
         final Rect bounds = new Rect(0, 0, 100, 100);
         mSplitController.setActivityStackAttributesCalculator(params ->
                 new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
         final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                "test1");
+                mOverlayContainer1.getOverlayTag(),
+                mOverlayContainer1.getTopNonFinishingActivity());
 
         assertWithMessage("overlayContainer1 must be updated since the new overlay container"
                 + " is launched with the same tag and task")
@@ -247,6 +249,22 @@
     }
 
     @Test
+    public void testCreateOrUpdateOverlay_sameTagAndTaskButNotActivity_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        mSplitController.setActivityStackAttributesCalculator(params ->
+                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                mOverlayContainer1.getOverlayTag(), mActivity);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is associated with different launching activity")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
         createExistingOverlayContainers();
 
@@ -294,7 +312,7 @@
                 new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
         final TaskFragmentContainer overlayContainer =
                 createOrUpdateOverlayTaskFragmentIfNeeded("test");
-        setupTaskFragmentInfo(overlayContainer, mActivity);
+        setupTaskFragmentInfo(overlayContainer, mActivity, true /* isVisible */);
 
         assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
                 .containsExactly(overlayContainer);
@@ -510,8 +528,9 @@
     }
 
     @Test
-    public void testApplyActivityStackAttributesForOverlayContainer() {
-        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+    public void testApplyActivityStackAttributesForOverlayContainerAssociatedWithActivity() {
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID,
+                TEST_TAG, true /* associatedWithLaunchingActivity */);
         final IBinder token = container.getTaskFragmentToken();
         final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
                 .setRelativeBounds(new Rect(0, 0, 200, 200))
@@ -527,7 +546,35 @@
                 WINDOWING_MODE_MULTI_WINDOW);
         verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
                 TaskFragmentAnimationParams.DEFAULT);
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+        // Set isolated navigation to false if the overlay container is associated with
+        // the launching activity.
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+        verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+    }
+
+    @Test
+    public void testApplyActivityStackAttributesForOverlayContainerWithoutAssociatedActivity() {
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG,
+                false /* associatedWithLaunchingActivity */);
+        final IBinder token = container.getTaskFragmentToken();
+        final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+                .setRelativeBounds(new Rect(0, 0, 200, 200))
+                .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+                .build();
+
+        mSplitPresenter.applyActivityStackAttributes(mTransaction, container,
+                attributes, null /* minDimensions */);
+
+        verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+                attributes.getRelativeBounds());
+        verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction,
+                container, WINDOWING_MODE_MULTI_WINDOW);
+        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+                TaskFragmentAnimationParams.DEFAULT);
+        // Set isolated navigation to false if the overlay container is associated with
+        // the launching activity.
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
+                container, true);
         verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
     }
 
@@ -573,16 +620,65 @@
         verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
     }
 
+    @Test
+    public void testFinishSelfWithActivityIfNeeded() {
+        TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+        container.finishSelfWithActivityIfNeeded(mTransaction, mActivity.getActivityToken());
+
+        verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+
+        TaskFragmentContainer overlayWithoutAssociation = createTestOverlayContainer(TASK_ID,
+                "test", false /* associateLaunchingActivity */);
+
+        overlayWithoutAssociation.finishSelfWithActivityIfNeeded(mTransaction,
+                mActivity.getActivityToken());
+
+        verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .contains(overlayWithoutAssociation);
+
+        TaskFragmentContainer overlayWithAssociation =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test");
+        overlayWithAssociation.setInfo(mTransaction, createMockTaskFragmentInfo(
+                overlayWithAssociation, mActivity, true /* isVisible */));
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .contains(overlayWithAssociation);
+        clearInvocations(mSplitPresenter);
+
+        overlayWithAssociation.finishSelfWithActivityIfNeeded(mTransaction, new Binder());
+
+        verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+
+        overlayWithAssociation.finishSelfWithActivityIfNeeded(mTransaction,
+                mActivity.getActivityToken());
+
+        verify(mSplitPresenter).cleanupContainer(mTransaction, overlayWithAssociation, false);
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .doesNotContain(overlayWithAssociation);
+    }
+
     /**
-     * A simplified version of {@link SplitController.ActivityStartMonitor
-     * #createOrUpdateOverlayTaskFragmentIfNeeded}
+     * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
      */
     @Nullable
     private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
         final Bundle launchOptions = new Bundle();
         launchOptions.putString(KEY_OVERLAY_TAG, tag);
+        return createOrUpdateOverlayTaskFragmentIfNeeded(tag, mActivity);
+    }
+
+    /**
+     * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
+     */
+    @Nullable
+    private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull String tag, @NonNull Activity activity) {
+        final Bundle launchOptions = new Bundle();
+        launchOptions.putString(KEY_OVERLAY_TAG, tag);
         return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
-                launchOptions, mIntent, mActivity);
+                launchOptions, mIntent, activity);
     }
 
     /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -590,23 +686,34 @@
     private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
         final TaskFragmentContainer container = mSplitController.newContainer(activity,
                 activity.getTaskId());
-        setupTaskFragmentInfo(container, activity);
+        setupTaskFragmentInfo(container, activity, false /* isVisible */);
         return container;
     }
 
     @NonNull
     private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+        return createTestOverlayContainer(taskId, tag,
+                true /* associateLaunchingActivity */);
+    }
+
+    // TODO(b/243518738): add more test coverage on overlay container without activity association
+    //  once we have use cases.
+    @NonNull
+    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
+                boolean associateLaunchingActivity) {
         Activity activity = createMockActivity();
         TaskFragmentContainer overlayContainer = mSplitController.newContainer(
                 null /* pendingAppearedActivity */, mIntent, activity, taskId,
-                null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
-        setupTaskFragmentInfo(overlayContainer, activity);
+                null /* pairedPrimaryContainer */, tag, Bundle.EMPTY,
+                associateLaunchingActivity);
+        setupTaskFragmentInfo(overlayContainer, activity, false /* isVisible */);
         return overlayContainer;
     }
 
     private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
-                                       @NonNull Activity activity) {
-        final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+                                       @NonNull Activity activity,
+                                       boolean isVisible) {
+        final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isVisible);
         container.setInfo(mTransaction, info);
         mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
     }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index c246a19..3441c2b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -227,13 +227,13 @@
 
         // When the activity is not finishing, do not clear the record.
         doReturn(false).when(mActivity).isFinishing();
-        mSplitController.onActivityDestroyed(mActivity);
+        mSplitController.onActivityDestroyed(mTransaction, mActivity);
 
         assertTrue(tf.hasActivity(mActivity.getActivityToken()));
 
         // Clear the record when the activity is finishing and destroyed.
         doReturn(true).when(mActivity).isFinishing();
-        mSplitController.onActivityDestroyed(mActivity);
+        mSplitController.onActivityDestroyed(mTransaction, mActivity);
 
         assertFalse(tf.hasActivity(mActivity.getActivityToken()));
     }
@@ -612,7 +612,7 @@
 
         assertFalse(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
-                anyString(), any());
+                anyString(), any(), anyBoolean());
     }
 
     @Test
@@ -775,7 +775,7 @@
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
-                anyString(), any());
+                anyString(), any(), anyBoolean());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -818,7 +818,7 @@
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
-                anyString(), any());
+                anyString(), any(), anyBoolean());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index cc00a49..0af4179 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -402,7 +402,7 @@
 
         assertTrue(container.hasActivity(mActivity.getActivityToken()));
 
-        taskContainer.onActivityDestroyed(mActivity.getActivityToken());
+        taskContainer.onActivityDestroyed(mTransaction, mActivity.getActivityToken());
 
         // It should not contain the destroyed Activity.
         assertFalse(container.hasActivity(mActivity.getActivityToken()));
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 4ee2c1a..c2c90c8 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -515,8 +515,20 @@
     <!-- The size of the icon shown in the resize veil. -->
     <dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen>
 
+    <!-- The with of the border around the app task for edge resizing, when
+         enable_windowing_edge_drag_resize is enabled. -->
+    <dimen name="desktop_mode_edge_handle">12dp</dimen>
+
+    <!-- The original width of the border around the app task for edge resizing, when
+         enable_windowing_edge_drag_resize is disabled. -->
     <dimen name="freeform_resize_handle">15dp</dimen>
 
+    <!-- The size of the corner region for drag resizing with touch, when a larger touch region is
+         appropriate. Applied when enable_windowing_edge_drag_resize is enabled. -->
+    <dimen name="desktop_mode_corner_resize_large">48dp</dimen>
+
+    <!-- The original size of the corner region for darg resizing, when
+         enable_windowing_edge_drag_resize is disabled. -->
     <dimen name="freeform_resize_corner">44dp</dimen>
 
     <!-- The width of the area at the sides of the screen where a freeform task will transition to
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index a32b435..4988a94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,6 +28,7 @@
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.Cuj.CujType;
 import com.android.wm.shell.common.InteractionJankMonitorUtils;
 
@@ -108,7 +109,8 @@
         }
     }
 
-    private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+    @VisibleForTesting
+    boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
         return apps.length > 0 && mCujType != NO_CUJ;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b933e5d..ff00a7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -241,6 +241,7 @@
                 mainChoreographer,
                 taskOrganizer,
                 displayController,
+                rootTaskDisplayAreaOrganizer,
                 syncQueue,
                 transitions);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 58942ec..487bbfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -588,7 +588,7 @@
             if (taskBoundsBeforeMaximize != null) {
                 destinationBounds.set(taskBoundsBeforeMaximize)
             } else {
-                getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+                destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
             }
         } else {
             // Save current bounds so that task can be restored back to original bounds if necessary
@@ -624,18 +624,14 @@
         }
     }
 
-    private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout, outBounds: Rect) {
+    private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
         // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
-        val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
-        // Update width and height with default desktop mode values
-        val desiredWidth = screenBounds.width().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
-        val desiredHeight = screenBounds.height().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
-        outBounds.set(0, 0, desiredWidth, desiredHeight)
-        // Center the task in screen bounds
-        outBounds.offset(
-            screenBounds.centerX() - outBounds.centerX(),
-            screenBounds.centerY() - outBounds.centerY()
-        )
+        val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+        val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+        val heightOffset = (displayLayout.height() - desiredHeight) / 2
+        val widthOffset = (displayLayout.width() - desiredWidth) / 2
+        return Rect(widthOffset, heightOffset,
+            desiredWidth + widthOffset, desiredHeight + heightOffset)
     }
 
     private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -889,8 +885,9 @@
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo
     ) {
-        val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
-        val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
+        val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+        val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
             // Display windowing is freeform, set to undefined and inherit it
             WINDOWING_MODE_UNDEFINED
         } else {
@@ -907,8 +904,9 @@
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo
     ) {
-        val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
-        val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
+        val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+        val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FULLSCREEN) {
             // Display windowing is fullscreen, set to undefined and inherit it
             WINDOWING_MODE_UNDEFINED
         } else {
@@ -1090,17 +1088,14 @@
      * @param taskInfo the task being dragged.
      * @param y height of drag, to be checked against status bar height.
      */
-    fun onDragPositioningEndThroughStatusBar(
-            inputCoordinates: PointF,
-            taskInfo: RunningTaskInfo,
-            freeformBounds: Rect
-    ) {
+    fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
         val indicator = visualIndicator ?: return
         val indicatorType = indicator
             .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
         when (indicatorType) {
             DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
-                finalizeDragToDesktop(taskInfo, freeformBounds)
+                val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+                finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
             }
             DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
                     DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index e8f58fe..62d195e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -37,4 +37,9 @@
      * Called when a running task vanishes.
      */
     void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+
+    /**
+     * Called when a running task changes.
+     */
+    void onRunningTaskChanged(in RunningTaskInfo taskInfo);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f9fcfac..0c99aed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.pm.PackageManager.FEATURE_PC;
 
+import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
 
@@ -26,7 +27,6 @@
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
-import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -86,7 +86,7 @@
     private final ActivityTaskManager mActivityTaskManager;
     private RecentsTransitionHandler mTransitionHandler = null;
     private IRecentTasksListener mListener;
-    private final boolean mIsDesktopMode;
+    private final boolean mPcFeatureEnabled;
 
     // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
     // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
@@ -133,7 +133,7 @@
         mShellController = shellController;
         mShellCommandHandler = shellCommandHandler;
         mActivityTaskManager = activityTaskManager;
-        mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
+        mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
         mMainExecutor = mainExecutor;
@@ -252,8 +252,10 @@
         notifyRunningTaskVanished(taskInfo);
     }
 
-    public void onTaskWindowingModeChanged(TaskInfo taskInfo) {
+    /** Notify listeners that the windowing mode of the given Task was updated. */
+    public void onTaskWindowingModeChanged(ActivityManager.RunningTaskInfo taskInfo) {
         notifyRecentTasksChanged();
+        notifyRunningTaskChanged(taskInfo);
     }
 
     @Override
@@ -278,7 +280,9 @@
      * Notify the running task listener that a task appeared on desktop environment.
      */
     private void notifyRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mListener == null || !mIsDesktopMode || taskInfo.realActivity == null) {
+        if (mListener == null
+                || !shouldEnableRunningTasksForDesktopMode()
+                || taskInfo.realActivity == null) {
             return;
         }
         try {
@@ -292,7 +296,9 @@
      * Notify the running task listener that a task was removed on desktop environment.
      */
     private void notifyRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mListener == null || !mIsDesktopMode || taskInfo.realActivity == null) {
+        if (mListener == null
+                || !shouldEnableRunningTasksForDesktopMode()
+                || taskInfo.realActivity == null) {
             return;
         }
         try {
@@ -302,6 +308,27 @@
         }
     }
 
+    /**
+     * Notify the running task listener that a task was changed on desktop environment.
+     */
+    private void notifyRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        if (mListener == null
+                || !shouldEnableRunningTasksForDesktopMode()
+                || taskInfo.realActivity == null) {
+            return;
+        }
+        try {
+            mListener.onRunningTaskChanged(taskInfo);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed call onRunningTaskChanged", e);
+        }
+    }
+
+    private boolean shouldEnableRunningTasksForDesktopMode() {
+        return mPcFeatureEnabled
+                || (DesktopModeStatus.isEnabled() && enableDesktopWindowingTaskbarRunningApps());
+    }
+
     @VisibleForTesting
     void registerRecentTasksListener(IRecentTasksListener listener) {
         mListener = listener;
@@ -476,6 +503,11 @@
             public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
                 mListener.call(l -> l.onRunningTaskVanished(taskInfo));
             }
+
+            @Override
+            public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+                mListener.call(l -> l.onRunningTaskChanged(taskInfo));
+            }
         };
 
         public IRecentTasksImpl(RecentTasksController controller) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index c59a1b4..87dc391 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,23 +19,30 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.provider.Settings;
 import android.util.SparseArray;
 import android.view.Choreographer;
+import android.view.Display;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.window.DisplayAreaInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -53,6 +60,7 @@
     private final Handler mMainHandler;
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final SyncTransactionQueue mSyncQueue;
     private final Transitions mTransitions;
     private TaskOperations mTaskOperations;
@@ -65,6 +73,7 @@
             Choreographer mainChoreographer,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             SyncTransactionQueue syncQueue,
             Transitions transitions) {
         mContext = context;
@@ -72,6 +81,7 @@
         mMainChoreographer = mainChoreographer;
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -158,10 +168,33 @@
     }
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
-        return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
-                && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
-                == WINDOWING_MODE_FREEFORM);
+        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            return true;
+        }
+        if (taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+            return false;
+        }
+        final DisplayAreaInfo rootDisplayAreaInfo =
+                mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId);
+        if (rootDisplayAreaInfo != null) {
+            return rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode()
+                    == WINDOWING_MODE_FREEFORM;
+        }
+
+        // It is possible that the rootDisplayAreaInfo is null when a task appears soon enough after
+        // a new display shows up, because TDA may appear after task appears in WM shell. Instead of
+        // fixing the synchronization issues, let's use other signals to "guess" the answer. It is
+        // OK in this context because no other captions other than the legacy developer option
+        // freeform and Kingyo/CF PC may use this class. WM shell should have full control over the
+        // condition where captions should show up in all new cases such as desktop mode, for which
+        // we should use different window decor view models. Ultimately Kingyo/CF PC may need to
+        // spin up their own window decor view model when they start to care about multiple
+        // displays.
+        if (isPc()) {
+            return true;
+        }
+        return taskInfo.displayId != Display.DEFAULT_DISPLAY
+                && forcesDesktopModeOnExternalDisplays();
     }
 
     private void createWindowDecoration(
@@ -231,7 +264,10 @@
                 mTaskOperations.minimizeTask(mTaskToken);
             } else if (id == R.id.maximize_window) {
                 RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
-                mTaskOperations.maximizeTask(taskInfo);
+                final DisplayAreaInfo rootDisplayAreaInfo =
+                        mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId);
+                mTaskOperations.maximizeTask(taskInfo,
+                        rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode());
             }
         }
 
@@ -305,4 +341,17 @@
             return true;
         }
     }
+
+    /**
+     * Returns if this device is a PC.
+     */
+    private boolean isPc() {
+        return mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
+    }
+
+    private boolean forcesDesktopModeOnExternalDisplays() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        return Settings.Global.getInt(resolver,
+                DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+    }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index beead6a..43fd32b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,17 +16,23 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+
 import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
+import android.util.Size;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -222,7 +228,6 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
-                    0 /* taskCornerRadius */,
                     mDecorationContainerSurface,
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
@@ -234,12 +239,10 @@
                 .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        final int resize_handle = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
-        final int resize_corner = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_corner);
-        mDragResizeListener.setGeometry(
-                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+        final Resources res = mResult.mRootView.getResources();
+        mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+                new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+                getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a0f9c6b..7649a782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -858,10 +858,7 @@
                         // as it likely will change.
                         relevantDecor.updateHoverAndPressStatus(ev);
                         mDesktopTasksController.onDragPositioningEndThroughStatusBar(
-                                new PointF(ev.getRawX(), ev.getRawY()),
-                                relevantDecor.mTaskInfo,
-                                calculateFreeformBounds(ev.getDisplayId(),
-                                        DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
+                                new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
                         mMoveToDesktopAnimator = null;
                         return;
                     } else {
@@ -913,23 +910,6 @@
         }
     }
 
-    /**
-     * Gets bounds of a scaled window centered relative to the screen bounds
-     * @param scale the amount to scale to relative to the Screen Bounds
-     */
-    private Rect calculateFreeformBounds(int displayId, float scale) {
-        // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
-        final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
-        final int screenWidth = displayLayout.width();
-        final int screenHeight = displayLayout.height();
-
-        final float adjustmentPercentage = (1f - scale) / 2;
-        return new Rect((int) (screenWidth * adjustmentPercentage),
-                (int) (screenHeight * adjustmentPercentage),
-                (int) (screenWidth * (adjustmentPercentage + scale)),
-                (int) (screenHeight * (adjustmentPercentage + scale)));
-    }
-
     @Nullable
     private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
         final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 963b130..2bbe530 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,6 +24,9 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -42,6 +45,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.util.Log;
+import android.util.Size;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -276,7 +280,6 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
-                    mRelayoutParams.mCornerRadius,
                     mDecorationContainerSurface,
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
@@ -288,15 +291,13 @@
                 .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        final int resize_handle = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
-        final int resize_corner = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_corner);
-
         // If either task geometry or position have changed, update this task's
         // exclusion region listener
+        final Resources res = mResult.mRootView.getResources();
         if (mDragResizeListener.setGeometry(
-                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
+                new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
+                        new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+                        getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
             updateExclusionRegion();
         }
@@ -427,7 +428,7 @@
         return mHandleMenu != null;
     }
 
-    boolean shouldResizeListenerHandleEvent(MotionEvent e, Point offset) {
+    boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
         return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 8ce2d6d..421ffd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -23,6 +23,9 @@
  * Callback called when receiving drag-resize or drag-move related input events.
  */
 public interface DragPositioningCallback {
+    /**
+     * Indicates the direction of resizing. May be combined together to indicate a diagonal drag.
+     */
     @IntDef(flag = true, value = {
             CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
     })
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 97eb4a4..9624d46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -30,6 +30,7 @@
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -39,6 +40,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Size;
 import android.view.Choreographer;
 import android.view.IWindowSession;
 import android.view.InputChannel;
@@ -55,6 +57,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -66,40 +69,20 @@
 class DragResizeInputListener implements AutoCloseable {
     private static final String TAG = "DragResizeInputListener";
     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
-    private final Context mContext;
-    private final Handler mHandler;
-    private final Choreographer mChoreographer;
-    private final InputManager mInputManager;
     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
 
     private final IBinder mClientToken;
 
-    private final InputTransferToken mInputTransferToken;
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
     private final TaskResizeInputEventReceiver mInputEventReceiver;
-    private final DragPositioningCallback mCallback;
 
     private final SurfaceControl mInputSinkSurface;
     private final IBinder mSinkClientToken;
     private final InputChannel mSinkInputChannel;
     private final DisplayController mDisplayController;
-
-    private int mTaskWidth;
-    private int mTaskHeight;
-    private int mResizeHandleThickness;
-    private int mCornerSize;
-    private int mTaskCornerRadius;
-
-    private Rect mLeftTopCornerBounds;
-    private Rect mRightTopCornerBounds;
-    private Rect mLeftBottomCornerBounds;
-    private Rect mRightBottomCornerBounds;
-
-    private int mDragPointerId = -1;
-    private DragDetector mDragDetector;
     private final Region mTouchRegion = new Region();
 
     DragResizeInputListener(
@@ -107,23 +90,17 @@
             Handler handler,
             Choreographer choreographer,
             int displayId,
-            int taskCornerRadius,
             SurfaceControl decorationSurface,
             DragPositioningCallback callback,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             DisplayController displayController) {
-        mInputManager = context.getSystemService(InputManager.class);
-        mContext = context;
-        mHandler = handler;
-        mChoreographer = choreographer;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mDisplayId = displayId;
-        mTaskCornerRadius = taskCornerRadius;
         mDecorationSurface = decorationSurface;
         mDisplayController = displayController;
         mClientToken = new Binder();
-        mInputTransferToken = new InputTransferToken();
+        final InputTransferToken inputTransferToken = new InputTransferToken();
         mInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
@@ -136,18 +113,19 @@
                     INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
                     null /* windowToken */,
-                    mInputTransferToken,
+                    inputTransferToken,
                     TAG + " of " + decorationSurface.toString(),
                     mInputChannel);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
 
-        mInputEventReceiver = new TaskResizeInputEventReceiver(
-                mInputChannel, mHandler, mChoreographer);
-        mCallback = callback;
-        mDragDetector = new DragDetector(mInputEventReceiver);
-        mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+        mInputEventReceiver = new TaskResizeInputEventReceiver(context, mInputChannel, callback,
+                handler, choreographer, () -> {
+            final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
+            return new Size(layout.width(), layout.height());
+        }, this::updateSinkInputChannel);
+        mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
 
         mInputSinkSurface = surfaceControlBuilderSupplier.get()
                 .setName("TaskInputSink of " + decorationSurface)
@@ -171,7 +149,7 @@
                     INPUT_FEATURE_NO_INPUT_CHANNEL,
                     TYPE_INPUT_CONSUMER,
                     null /* windowToken */,
-                    mInputTransferToken,
+                    inputTransferToken,
                     "TaskInputSink of " + decorationSurface,
                     mSinkInputChannel);
         } catch (RemoteException e) {
@@ -182,86 +160,26 @@
     /**
      * Updates the geometry (the touch region) of this drag resize handler.
      *
-     * @param taskWidth The width of the task.
-     * @param taskHeight The height of the task.
-     * @param resizeHandleThickness The thickness of the resize handle in pixels.
-     * @param cornerSize The size of the resize handle centered in each corner.
-     * @param touchSlop The distance in pixels user has to drag with touch for it to register as
-     *                  a resize action.
+     * @param incomingGeometry The geometry update to apply for this task's drag resize regions.
+     * @param touchSlop        The distance in pixels user has to drag with touch for it to register
+     *                         as a resize action.
      * @return whether the geometry has changed or not
      */
-    boolean setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
-            int touchSlop) {
-        if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
-                && mResizeHandleThickness == resizeHandleThickness
-                && mCornerSize == cornerSize) {
+    boolean setGeometry(@NonNull DragResizeWindowGeometry incomingGeometry, int touchSlop) {
+        DragResizeWindowGeometry geometry = mInputEventReceiver.getGeometry();
+        if (incomingGeometry.equals(geometry)) {
+            // Geometry hasn't changed size so skip all updates.
             return false;
+        } else {
+            geometry = incomingGeometry;
         }
-
-        mTaskWidth = taskWidth;
-        mTaskHeight = taskHeight;
-        mResizeHandleThickness = resizeHandleThickness;
-        mCornerSize = cornerSize;
-        mDragDetector.setTouchSlop(touchSlop);
+        mInputEventReceiver.setTouchSlop(touchSlop);
 
         mTouchRegion.setEmpty();
-        final Rect topInputBounds = new Rect(
-                -mResizeHandleThickness,
-                -mResizeHandleThickness,
-                mTaskWidth + mResizeHandleThickness,
-                0);
-        mTouchRegion.union(topInputBounds);
-
-        final Rect leftInputBounds = new Rect(
-                -mResizeHandleThickness,
-                0,
-                0,
-                mTaskHeight);
-        mTouchRegion.union(leftInputBounds);
-
-        final Rect rightInputBounds = new Rect(
-                mTaskWidth,
-                0,
-                mTaskWidth + mResizeHandleThickness,
-                mTaskHeight);
-        mTouchRegion.union(rightInputBounds);
-
-        final Rect bottomInputBounds = new Rect(
-                -mResizeHandleThickness,
-                mTaskHeight,
-                mTaskWidth + mResizeHandleThickness,
-                mTaskHeight + mResizeHandleThickness);
-        mTouchRegion.union(bottomInputBounds);
-
-        // Set up touch areas in each corner.
-        int cornerRadius = mCornerSize / 2;
-        mLeftTopCornerBounds = new Rect(
-                -cornerRadius,
-                -cornerRadius,
-                cornerRadius,
-                cornerRadius);
-        mTouchRegion.union(mLeftTopCornerBounds);
-
-        mRightTopCornerBounds = new Rect(
-                mTaskWidth - cornerRadius,
-                -cornerRadius,
-                mTaskWidth + cornerRadius,
-                cornerRadius);
-        mTouchRegion.union(mRightTopCornerBounds);
-
-        mLeftBottomCornerBounds = new Rect(
-                -cornerRadius,
-                mTaskHeight - cornerRadius,
-                cornerRadius,
-                mTaskHeight + cornerRadius);
-        mTouchRegion.union(mLeftBottomCornerBounds);
-
-        mRightBottomCornerBounds = new Rect(
-                mTaskWidth - cornerRadius,
-                mTaskHeight - cornerRadius,
-                mTaskWidth + cornerRadius,
-                mTaskHeight + cornerRadius);
-        mTouchRegion.union(mRightBottomCornerBounds);
+        // Apply the geometry to the touch region.
+        geometry.union(mTouchRegion);
+        mInputEventReceiver.setGeometry(geometry);
+        mInputEventReceiver.setTouchRegion(mTouchRegion);
 
         try {
             mWindowSession.updateInputChannel(
@@ -276,8 +194,9 @@
             e.rethrowFromSystemServer();
         }
 
+        final Size taskSize = geometry.getTaskSize();
         mSurfaceControlTransactionSupplier.get()
-                .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+                .setWindowCrop(mInputSinkSurface, taskSize.getWidth(), taskSize.getHeight())
                 .apply();
         // The touch region of the TaskInputSink should be the touch region of this
         // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
@@ -290,21 +209,16 @@
         // issue. However, were there touchscreen-only a region out of the task bounds, mouse
         // gestures will become no-op in that region, even though the mouse gestures may appear to
         // be performed on the input window behind the resize handle.
-        mTouchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+        mTouchRegion.op(0, 0, taskSize.getWidth(), taskSize.getHeight(), Region.Op.DIFFERENCE);
         updateSinkInputChannel(mTouchRegion);
         return true;
     }
 
     /**
-     * Generate a Region that encapsulates all 4 corner handles
+     * Generate a Region that encapsulates all 4 corner handles and window edges.
      */
-    Region getCornersRegion() {
-        Region region = new Region();
-        region.union(mLeftTopCornerBounds);
-        region.union(mLeftBottomCornerBounds);
-        region.union(mRightTopCornerBounds);
-        region.union(mRightBottomCornerBounds);
-        return region;
+    @NonNull Region getCornersRegion() {
+        return mInputEventReceiver.getCornersRegion();
     }
 
     private void updateSinkInputChannel(Region region) {
@@ -322,7 +236,7 @@
         }
     }
 
-    boolean shouldHandleEvent(MotionEvent e, Point offset) {
+    boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
         return mInputEventReceiver.shouldHandleEvent(e, offset);
     }
 
@@ -351,19 +265,37 @@
                 .apply();
     }
 
-    private class TaskResizeInputEventReceiver extends InputEventReceiver
-            implements DragDetector.MotionEventHandler {
-        private final Choreographer mChoreographer;
-        private final Runnable mConsumeBatchEventRunnable;
+    private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
+            DragDetector.MotionEventHandler {
+        @NonNull private final Context mContext;
+        private final InputManager mInputManager;
+        @NonNull private final InputChannel mInputChannel;
+        @NonNull private final DragPositioningCallback mCallback;
+        @NonNull private final Choreographer mChoreographer;
+        @NonNull private final Runnable mConsumeBatchEventRunnable;
+        @NonNull private final DragDetector mDragDetector;
+        @NonNull private final Supplier<Size> mDisplayLayoutSizeSupplier;
+        @NonNull private final Consumer<Region> mTouchRegionConsumer;
+        private final Rect mTmpRect = new Rect();
         private boolean mConsumeBatchEventScheduled;
+        private DragResizeWindowGeometry mDragResizeWindowGeometry;
+        private Region mTouchRegion;
         private boolean mShouldHandleEvents;
         private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
         private Rect mDragStartTaskBounds;
-        private final Rect mTmpRect = new Rect();
+        private int mDragPointerId = -1;
 
-        private TaskResizeInputEventReceiver(
-                InputChannel inputChannel, Handler handler, Choreographer choreographer) {
+        private TaskResizeInputEventReceiver(@NonNull Context context,
+                @NonNull InputChannel inputChannel,
+                @NonNull DragPositioningCallback callback, @NonNull Handler handler,
+                @NonNull Choreographer choreographer,
+                @NonNull Supplier<Size> displayLayoutSizeSupplier,
+                @NonNull Consumer<Region> touchRegionConsumer) {
             super(inputChannel, handler.getLooper());
+            mContext = context;
+            mInputManager = context.getSystemService(InputManager.class);
+            mInputChannel = inputChannel;
+            mCallback = callback;
             mChoreographer = choreographer;
 
             mConsumeBatchEventRunnable = () -> {
@@ -376,6 +308,48 @@
                     scheduleConsumeBatchEvent();
                 }
             };
+
+            mDragDetector = new DragDetector(this);
+            mDisplayLayoutSizeSupplier = displayLayoutSizeSupplier;
+            mTouchRegionConsumer = touchRegionConsumer;
+        }
+
+        /**
+         * Returns the geometry of the areas to drag resize.
+         */
+        DragResizeWindowGeometry getGeometry() {
+            return mDragResizeWindowGeometry;
+        }
+
+        /**
+         * Updates the geometry of the areas to drag resize.
+         */
+        void setGeometry(@NonNull DragResizeWindowGeometry dragResizeWindowGeometry) {
+            mDragResizeWindowGeometry = dragResizeWindowGeometry;
+        }
+
+        /**
+         * Sets how much slop to allow for touches.
+         */
+        void setTouchSlop(int touchSlop) {
+            mDragDetector.setTouchSlop(touchSlop);
+        }
+
+        /**
+         * Updates the region accepting input for drag resizing the task.
+         */
+        void setTouchRegion(@NonNull Region touchRegion) {
+            mTouchRegion = touchRegion;
+        }
+
+        /**
+         * Returns the union of all regions that can be touched for drag resizing; the corners and
+         * window edges.
+         */
+        @NonNull Region getCornersRegion() {
+            Region region = new Region();
+            mDragResizeWindowGeometry.union(region);
+            return region;
         }
 
         @Override
@@ -416,14 +390,15 @@
             boolean isTouch = isTouchEvent(e);
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
-                    mShouldHandleEvents = shouldHandleEvent(e, isTouch, new Point() /* offset */);
+                    mShouldHandleEvents = mDragResizeWindowGeometry.shouldHandleEvent(e, isTouch,
+                            new Point() /* offset */);
                     if (mShouldHandleEvents) {
                         mDragPointerId = e.getPointerId(0);
                         float x = e.getX(0);
                         float y = e.getY(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
-                        int ctrlType = calculateCtrlType(isTouch, x, y);
+                        int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
                         mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
                                 rawX, rawY);
                         // Increase the input sink region to cover the whole screen; this is to
@@ -455,7 +430,7 @@
                         // If taskBounds has changed, setGeometry will be called and update the
                         // sink region. Otherwise, we should revert it here.
                         if (taskBounds.equals(mDragStartTaskBounds)) {
-                            updateSinkInputChannel(mTouchRegion);
+                            mTouchRegionConsumer.accept(mTouchRegion);
                         }
                     }
                     mShouldHandleEvents = false;
@@ -480,125 +455,20 @@
 
         private void updateInputSinkRegionForDrag(Rect taskBounds) {
             mTmpRect.set(taskBounds);
-            final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
-            final Region dragTouchRegion = new Region(-taskBounds.left,
-                    -taskBounds.top,
-                    -taskBounds.left + layout.width(),
-                    -taskBounds.top + layout.height());
+            final Size displayLayoutSize = mDisplayLayoutSizeSupplier.get();
+            final Region dragTouchRegion = new Region(-taskBounds.left, -taskBounds.top,
+                    -taskBounds.left + displayLayoutSize.getWidth(),
+                    -taskBounds.top + displayLayoutSize.getHeight());
             // Remove the localized task bounds from the touch region.
             mTmpRect.offsetTo(0, 0);
             dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
-            updateSinkInputChannel(dragTouchRegion);
-        }
-
-        private boolean isInCornerBounds(float xf, float yf) {
-            return calculateCornersCtrlType(xf, yf) != 0;
-        }
-
-        private boolean isInResizeHandleBounds(float x, float y) {
-            return calculateResizeHandlesCtrlType(x, y) != 0;
-        }
-
-        @DragPositioningCallback.CtrlType
-        private int calculateCtrlType(boolean isTouch, float x, float y) {
-            if (isTouch) {
-                return calculateCornersCtrlType(x, y);
-            }
-            return calculateResizeHandlesCtrlType(x, y);
-        }
-
-        @DragPositioningCallback.CtrlType
-        private int calculateResizeHandlesCtrlType(float x, float y) {
-            int ctrlType = 0;
-            // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
-            // sides will use the bounds specified in setGeometry and not go into task bounds.
-            if (x < mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_LEFT;
-            }
-            if (x > mTaskWidth - mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_RIGHT;
-            }
-            if (y < mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_TOP;
-            }
-            if (y > mTaskHeight - mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_BOTTOM;
-            }
-            // Check distances from the center if it's in one of four corners.
-            if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
-                    && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
-                return checkDistanceFromCenter(ctrlType, x, y);
-            }
-            // Otherwise, we should make sure we don't resize tasks inside task bounds.
-            return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
-        }
-
-        // If corner input is not within appropriate distance of corner radius, do not use it.
-        // If input is not on a corner or is within valid distance, return ctrlType.
-        @DragPositioningCallback.CtrlType
-        private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
-                float x, float y) {
-            int centerX;
-            int centerY;
-
-            // Determine center of rounded corner circle; this is simply the corner if radius is 0.
-            switch (ctrlType) {
-                case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
-                    centerX = mTaskCornerRadius;
-                    centerY = mTaskCornerRadius;
-                    break;
-                }
-                case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
-                    centerX = mTaskCornerRadius;
-                    centerY = mTaskHeight - mTaskCornerRadius;
-                    break;
-                }
-                case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
-                    centerX = mTaskWidth - mTaskCornerRadius;
-                    centerY = mTaskCornerRadius;
-                    break;
-                }
-                case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
-                    centerX = mTaskWidth - mTaskCornerRadius;
-                    centerY = mTaskHeight - mTaskCornerRadius;
-                    break;
-                }
-                default: {
-                    throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
-                            + Integer.toHexString(ctrlType));
-                }
-            }
-            double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
-
-            if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
-                    && distanceFromCenter >= mTaskCornerRadius) {
-                return ctrlType;
-            }
-            return 0;
-        }
-
-        @DragPositioningCallback.CtrlType
-        private int calculateCornersCtrlType(float x, float y) {
-            int xi = (int) x;
-            int yi = (int) y;
-            if (mLeftTopCornerBounds.contains(xi, yi)) {
-                return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
-            }
-            if (mLeftBottomCornerBounds.contains(xi, yi)) {
-                return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
-            }
-            if (mRightTopCornerBounds.contains(xi, yi)) {
-                return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
-            }
-            if (mRightBottomCornerBounds.contains(xi, yi)) {
-                return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
-            }
-            return 0;
+            mTouchRegionConsumer.accept(dragTouchRegion);
         }
 
         private void updateCursorType(int displayId, int deviceId, int pointerId, float x,
                 float y) {
-            @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
+            @DragPositioningCallback.CtrlType int ctrlType =
+                    mDragResizeWindowGeometry.calculateCtrlType(/* isTouch= */ false, x, y);
 
             int cursorType = PointerIcon.TYPE_DEFAULT;
             switch (ctrlType) {
@@ -640,19 +510,7 @@
         }
 
         private boolean shouldHandleEvent(MotionEvent e, Point offset) {
-            return shouldHandleEvent(e, isTouchEvent(e), offset);
-        }
-
-        private boolean shouldHandleEvent(MotionEvent e, boolean isTouch, Point offset) {
-            boolean result;
-            final float x = e.getX(0) + offset.x;
-            final float y = e.getY(0) + offset.y;
-            if (isTouch) {
-                result = isInCornerBounds(x, y);
-            } else {
-                result = isInResizeHandleBounds(x, y);
-            }
-            return result;
+            return mDragResizeWindowGeometry.shouldHandleEvent(e, offset);
         }
 
         private boolean isTouchEvent(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
new file mode 100644
index 0000000..eafb569
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -0,0 +1,434 @@
+/*
+ * 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.wm.shell.windowdecor;
+
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+import static com.android.window.flags.Flags.enableWindowingEdgeDragResize;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Size;
+import android.view.MotionEvent;
+
+import com.android.wm.shell.R;
+
+import java.util.Objects;
+
+/**
+ * Geometry for a drag resize region for a particular window.
+ */
+final class DragResizeWindowGeometry {
+    private final int mTaskCornerRadius;
+    private final Size mTaskSize;
+    // The size of the handle applied to the edges of the window, for the user to drag resize.
+    private final int mResizeHandleThickness;
+    // The task corners to permit drag resizing with a course input, such as touch.
+
+    private final @NonNull TaskCorners mLargeTaskCorners;
+    // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
+    private final @NonNull TaskCorners mFineTaskCorners;
+    // The bounds for each edge drag region, which can resize the task in one direction.
+    private final @NonNull Rect mTopEdgeBounds;
+    private final @NonNull Rect mLeftEdgeBounds;
+    private final @NonNull Rect mRightEdgeBounds;
+    private final @NonNull Rect mBottomEdgeBounds;
+
+    DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
+            int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
+        mTaskCornerRadius = taskCornerRadius;
+        mTaskSize = taskSize;
+        mResizeHandleThickness = resizeHandleThickness;
+
+        mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
+        mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
+
+        // Save touch areas for each edge.
+        mTopEdgeBounds = new Rect(
+                -mResizeHandleThickness,
+                -mResizeHandleThickness,
+                mTaskSize.getWidth() + mResizeHandleThickness,
+                0);
+        mLeftEdgeBounds = new Rect(
+                -mResizeHandleThickness,
+                0,
+                0,
+                mTaskSize.getHeight());
+        mRightEdgeBounds = new Rect(
+                mTaskSize.getWidth(),
+                0,
+                mTaskSize.getWidth() + mResizeHandleThickness,
+                mTaskSize.getHeight());
+        mBottomEdgeBounds = new Rect(
+                -mResizeHandleThickness,
+                mTaskSize.getHeight(),
+                mTaskSize.getWidth() + mResizeHandleThickness,
+                mTaskSize.getHeight() + mResizeHandleThickness);
+    }
+
+    /**
+     * Returns the resource value to use for the resize handle on the edge of the window.
+     */
+    static int getResizeEdgeHandleSize(@NonNull Resources res) {
+        return enableWindowingEdgeDragResize()
+                ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+                : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
+    }
+
+    /**
+     * Returns the resource value to use for course input, such as touch, that benefits from a large
+     * square on each of the window's corners.
+     */
+    static int getLargeResizeCornerSize(@NonNull Resources res) {
+        return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large);
+    }
+
+    /**
+     * Returns the resource value to use for fine input, such as stylus, that can use a smaller
+     * square on each of the window's corners.
+     */
+    static int getFineResizeCornerSize(@NonNull Resources res) {
+        return res.getDimensionPixelSize(R.dimen.freeform_resize_corner);
+    }
+
+    /**
+     * Returns the size of the task this geometry is calculated for.
+     */
+    @NonNull Size getTaskSize() {
+        // Safe to return directly since size is immutable.
+        return mTaskSize;
+    }
+
+    /**
+     * Returns the union of all regions that can be touched for drag resizing; the corners window
+     * and window edges.
+     */
+    void union(@NonNull Region region) {
+        // Apply the edge resize regions.
+        region.union(mTopEdgeBounds);
+        region.union(mLeftEdgeBounds);
+        region.union(mRightEdgeBounds);
+        region.union(mBottomEdgeBounds);
+
+        if (enableWindowingEdgeDragResize()) {
+            // Apply the corners as well for the larger corners, to ensure we capture all possible
+            // touches.
+            mLargeTaskCorners.union(region);
+        } else {
+            // Only apply fine corners for the legacy approach.
+            mFineTaskCorners.union(region);
+        }
+    }
+
+    /**
+     * Returns if this MotionEvent should be handled, based on its source and position.
+     */
+    boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+        return shouldHandleEvent(e, isTouchEvent(e), offset);
+    }
+
+    /**
+     * Returns if this MotionEvent should be handled, based on its source and position.
+     */
+    boolean shouldHandleEvent(@NonNull MotionEvent e, boolean isTouch, @NonNull Point offset) {
+        final float x = e.getX(0) + offset.x;
+        final float y = e.getY(0) + offset.y;
+
+        if (enableWindowingEdgeDragResize()) {
+            // First check if touch falls within a corner.
+            // Large corner bounds are used for course input like touch, otherwise fine bounds.
+            boolean result = isTouch
+                    ? isInCornerBounds(mLargeTaskCorners, x, y)
+                    : isInCornerBounds(mFineTaskCorners, x, y);
+            // Check if touch falls within the edge resize handle, since edge resizing can apply
+            // for any input source.
+            if (!result) {
+                result = isInEdgeResizeBounds(x, y);
+            }
+            return result;
+        } else {
+            // Legacy uses only fine corners for touch, and edges only for non-touch input.
+            return isTouch
+                    ? isInCornerBounds(mFineTaskCorners, x, y)
+                    : isInEdgeResizeBounds(x, y);
+        }
+    }
+
+    private boolean isTouchEvent(@NonNull MotionEvent e) {
+        return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+    }
+
+    private boolean isInCornerBounds(TaskCorners corners, float xf, float yf) {
+        return corners.calculateCornersCtrlType(xf, yf) != 0;
+    }
+
+    private boolean isInEdgeResizeBounds(float x, float y) {
+        return calculateEdgeResizeCtrlType(x, y) != 0;
+    }
+
+    /**
+     * Returns the control type for the drag-resize, based on the touch regions and this
+     * MotionEvent's coordinates.
+     */
+    @DragPositioningCallback.CtrlType
+    int calculateCtrlType(boolean isTouch, float x, float y) {
+        if (enableWindowingEdgeDragResize()) {
+            // First check if touch falls within a corner.
+            // Large corner bounds are used for course input like touch, otherwise fine bounds.
+            int ctrlType = isTouch
+                    ? mLargeTaskCorners.calculateCornersCtrlType(x, y)
+                    : mFineTaskCorners.calculateCornersCtrlType(x, y);
+            // Check if touch falls within the edge resize handle, since edge resizing can apply
+            // for any input source.
+            if (ctrlType == CTRL_TYPE_UNDEFINED) {
+                ctrlType = calculateEdgeResizeCtrlType(x, y);
+            }
+            return ctrlType;
+        } else {
+            // Legacy uses only fine corners for touch, and edges only for non-touch input.
+            return isTouch
+                    ? mFineTaskCorners.calculateCornersCtrlType(x, y)
+                    : calculateEdgeResizeCtrlType(x, y);
+        }
+    }
+
+    @DragPositioningCallback.CtrlType
+    private int calculateEdgeResizeCtrlType(float x, float y) {
+        int ctrlType = CTRL_TYPE_UNDEFINED;
+        // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
+        // sides will use the bounds specified in setGeometry and not go into task bounds.
+        if (x < mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_LEFT;
+        }
+        if (x > mTaskSize.getWidth() - mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_RIGHT;
+        }
+        if (y < mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_TOP;
+        }
+        if (y > mTaskSize.getHeight() - mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_BOTTOM;
+        }
+        // If the touch is within one of the four corners, check if it is within the bounds of the
+        // // handle.
+        if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+                && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+            return checkDistanceFromCenter(ctrlType, x, y);
+        }
+        // Otherwise, we should make sure we don't resize tasks inside task bounds.
+        return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight())
+                ? ctrlType : CTRL_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Return {@code ctrlType} if the corner input is outside the (potentially rounded) corner of
+     * the task, and within the thickness of the resize handle. Otherwise, return 0.
+     */
+    @DragPositioningCallback.CtrlType
+    private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, float x,
+            float y) {
+        final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType);
+        double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y);
+
+        if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+                && distanceFromCenter >= mTaskCornerRadius) {
+            return ctrlType;
+        }
+        return CTRL_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Returns center of rounded corner circle; this is simply the corner if radius is 0.
+     */
+    private Point calculateCenterForCornerRadius(@DragPositioningCallback.CtrlType int ctrlType) {
+        int centerX;
+        int centerY;
+
+        switch (ctrlType) {
+            case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+                centerX = mTaskCornerRadius;
+                centerY = mTaskCornerRadius;
+                break;
+            }
+            case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+                centerX = mTaskCornerRadius;
+                centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+                break;
+            }
+            case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+                centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+                centerY = mTaskCornerRadius;
+                break;
+            }
+            case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+                centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+                centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+                break;
+            }
+            default: {
+                throw new IllegalArgumentException(
+                        "ctrlType should be complex, but it's 0x" + Integer.toHexString(ctrlType));
+            }
+        }
+        return new Point(centerX, centerY);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+        if (this == obj) return true;
+        if (!(obj instanceof DragResizeWindowGeometry other)) return false;
+
+        return this.mTaskCornerRadius == other.mTaskCornerRadius
+                && this.mTaskSize.equals(other.mTaskSize)
+                && this.mResizeHandleThickness == other.mResizeHandleThickness
+                && this.mFineTaskCorners.equals(other.mFineTaskCorners)
+                && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
+                && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+                && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+                && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+                && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mTaskCornerRadius,
+                mTaskSize,
+                mResizeHandleThickness,
+                mFineTaskCorners,
+                mLargeTaskCorners,
+                mTopEdgeBounds,
+                mLeftEdgeBounds,
+                mRightEdgeBounds,
+                mBottomEdgeBounds);
+    }
+
+    /**
+     * Representation of the drag resize regions at the corner of the window.
+     */
+    private static class TaskCorners {
+        // The size of the square applied to the corners of the window, for the user to drag
+        // resize.
+        private final int mCornerSize;
+        // The square for each corner.
+        private final @NonNull Rect mLeftTopCornerBounds;
+        private final @NonNull Rect mRightTopCornerBounds;
+        private final @NonNull Rect mLeftBottomCornerBounds;
+        private final @NonNull Rect mRightBottomCornerBounds;
+
+        TaskCorners(@NonNull Size taskSize, int cornerSize) {
+            mCornerSize = cornerSize;
+            final int cornerRadius = cornerSize / 2;
+            mLeftTopCornerBounds = new Rect(
+                    -cornerRadius,
+                    -cornerRadius,
+                    cornerRadius,
+                    cornerRadius);
+
+            mRightTopCornerBounds = new Rect(
+                    taskSize.getWidth() - cornerRadius,
+                    -cornerRadius,
+                    taskSize.getWidth() + cornerRadius,
+                    cornerRadius);
+
+            mLeftBottomCornerBounds = new Rect(
+                    -cornerRadius,
+                    taskSize.getHeight() - cornerRadius,
+                    cornerRadius,
+                    taskSize.getHeight() + cornerRadius);
+
+            mRightBottomCornerBounds = new Rect(
+                    taskSize.getWidth() - cornerRadius,
+                    taskSize.getHeight() - cornerRadius,
+                    taskSize.getWidth() + cornerRadius,
+                    taskSize.getHeight() + cornerRadius);
+        }
+
+        /**
+         * Updates the region to include all four corners.
+         */
+        void union(Region region) {
+            region.union(mLeftTopCornerBounds);
+            region.union(mRightTopCornerBounds);
+            region.union(mLeftBottomCornerBounds);
+            region.union(mRightBottomCornerBounds);
+        }
+
+        /**
+         * Returns the control type based on the position of the {@code MotionEvent}'s coordinates.
+         */
+        @DragPositioningCallback.CtrlType
+        int calculateCornersCtrlType(float x, float y) {
+            int xi = (int) x;
+            int yi = (int) y;
+            if (mLeftTopCornerBounds.contains(xi, yi)) {
+                return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
+            }
+            if (mLeftBottomCornerBounds.contains(xi, yi)) {
+                return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
+            }
+            if (mRightTopCornerBounds.contains(xi, yi)) {
+                return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
+            }
+            if (mRightBottomCornerBounds.contains(xi, yi)) {
+                return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
+            }
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "TaskCorners of size " + mCornerSize + " for the"
+                    + " top left " + mLeftTopCornerBounds
+                    + " top right " + mRightTopCornerBounds
+                    + " bottom left " + mLeftBottomCornerBounds
+                    + " bottom right " + mRightBottomCornerBounds;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) return false;
+            if (this == obj) return true;
+            if (!(obj instanceof TaskCorners other)) return false;
+
+            return this.mCornerSize == other.mCornerSize
+                    && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds)
+                    && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds)
+                    && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds)
+                    && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(
+                    mCornerSize,
+                    mLeftTopCornerBounds,
+                    mRightTopCornerBounds,
+                    mLeftBottomCornerBounds,
+                    mRightBottomCornerBounds);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index d0fcd86..1763e4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -91,14 +91,12 @@
         }
     }
 
-    void maximizeTask(RunningTaskInfo taskInfo) {
+    void maximizeTask(RunningTaskInfo taskInfo, int containerWindowingMode) {
         WindowContainerTransaction wct = new WindowContainerTransaction();
         int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
                 ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
-        int displayWindowingMode =
-                taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
         wct.setWindowingMode(taskInfo.token,
-                targetWindowingMode == displayWindowingMode
+                targetWindowingMode == containerWindowingMode
                         ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
         if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
             wct.setBounds(taskInfo.token, null);
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index d64bfed..b85d793 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -33,7 +33,7 @@
 /**
  * Test entering pip from an app via auto-enter property when navigating to home.
  *
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipOnGoToHomeTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index 371fee2..d059211 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -30,7 +30,7 @@
 /**
  * Test auto entering pip using a source rect hint.
  *
- * To run this test: `atest AutoEnterPipWithSourceRectHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipWithSourceRectHintTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 1c0820a..a5e0550 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -31,7 +31,7 @@
 /**
  * Test closing a pip window by swiping it to the bottom-center of the screen
  *
- * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ClosePipBySwipingDownTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 860307f..d177624 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -30,7 +30,7 @@
 /**
  * Test closing a pip window via the dismiss button
  *
- * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ClosePipWithDismissButtonTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index c554161..a86803d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -31,7 +31,7 @@
 /**
  * Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
  *
- * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipOnUserLeaveHintTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 270ebf5..a0a61fe2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -46,7 +46,7 @@
 /**
  * Test entering pip while changing orientation (from app in landscape to pip window in portrait)
  *
- * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipToOtherOrientation`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index f97d8d1..d92f55a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -28,7 +28,7 @@
 /**
  * Test entering pip from an app by interacting with the app UI
  *
- * To run this test: `atest WMShellFlickerTests:EnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipViaAppUiButtonTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 47bf418..8c0817d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -28,7 +28,7 @@
 /**
  * Test expanding a pip window back to full screen via the expand button
  *
- * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaExpandButtonTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index a356e68..90a9623 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -28,7 +28,7 @@
 /**
  * Test expanding a pip window back to full screen via an intent
  *
- * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaIntentTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index eeff167..9306c77 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -33,7 +33,7 @@
 /**
  * Test expanding a pip window by double-clicking it
  *
- * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExpandPipOnDoubleClickTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index 12e395d..cb8ee27 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -39,7 +39,7 @@
 /**
  * Test entering pip from an app via auto-enter property when navigating to home from split screen.
  *
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenAutoEnterPipOnGoToHomeTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index f81e849..f2f10ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -40,7 +40,7 @@
 /**
  * Test entering pip from an app via auto-enter property when navigating to home from split screen.
  *
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenEnterPipOnUserLeaveHintTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 9b74622..265eb44 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -32,7 +32,7 @@
 /**
  * Test Pip movement with Launcher shelf height change (increase).
  *
- * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip3:MovePipDownOnShelfHeightChange`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index ad3c69e..04fedf4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -34,7 +34,10 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
+/**
+ * Test Pip launch. To run this test:
+ * `atest WMShellFlickerTestsPip3:MovePipOnImeVisibilityChangeTest`
+ */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 490ebd1..8d6be64 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -32,7 +32,7 @@
 /**
  * Test Pip movement with Launcher shelf height change (decrease).
  *
- * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip3:MovePipUpOnShelfHeightChangeTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index 9a6dacb..ed2a0a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -41,8 +41,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test exiting Pip with orientation changes. To run this test: `atest
- * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ * Test exiting Pip with orientation changes. To run this test:
+ * `atest WMShellFlickerTestsPip1:SetRequestedOrientationWhilePinned`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index d2f803e..9109eaf 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -35,7 +35,7 @@
 /**
  * Test Pip Stack in bounds after rotations.
  *
- * To run this test: `atest WMShellFlickerTests:PipRotationTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ShowPipAndRotateDisplay`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 32c0703..13f95cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -39,7 +39,7 @@
     static_libs: [
         "WindowManager-Shell",
         "junit",
-        "flag-junit-base",
+        "flag-junit",
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
@@ -55,6 +55,9 @@
         "platform-test-annotations",
         "servicestests-utils",
         "com_android_wm_shell_flags_lib",
+        "guava-android-testlib",
+        "com.android.window.flags.window-aconfig-java",
+        "platform-test-annotations",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 4061763..65169e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -596,6 +596,7 @@
 
         // Set up the monitoring objects.
         doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+        doReturn(false).when(animationRunner).shouldMonitorCUJ(any());
         doReturn(runner).when(animationRunner).getRunner();
         doReturn(callback).when(animationRunner).getCallback();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 93a967e..4d0f11b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -161,6 +161,10 @@
                 (i.arguments.first() as Rect).set(STABLE_BOUNDS)
             }
 
+        val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+        tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+
         controller = createController()
         controller.setSplitScreenController(splitScreenController)
 
@@ -336,9 +340,10 @@
     }
 
     @Test
-    fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
+    fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
-        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+        tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
         controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -346,9 +351,10 @@
     }
 
     @Test
-    fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+    fun moveToDesktop_tdaFreeform_windowingModeSetToUndefined() {
         val task = setUpFullscreenTask()
-        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+        tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
         controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -481,9 +487,10 @@
     }
 
     @Test
-    fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
+    fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
         val task = setUpFreeformTask()
-        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+        tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
         controller.moveToFullscreen(task.taskId)
         val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -491,9 +498,10 @@
     }
 
     @Test
-    fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+    fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
         val task = setUpFreeformTask()
-        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+        tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
         controller.moveToFullscreen(task.taskId)
         val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -684,7 +692,7 @@
                 createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
             )
         assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
-            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+            .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
     }
 
     @Test
@@ -694,7 +702,7 @@
         val task = createFreeformTask()
         val result = controller.handleRequest(Binder(), createTransition(task))
         assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
-            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+            .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
     }
 
     @Test
@@ -706,7 +714,7 @@
 
         val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
         assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode)
-            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+            .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
     }
 
     @Test
@@ -792,7 +800,7 @@
 
         val result = controller.handleRequest(Binder(), createTransition(task))
         assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+                .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
     }
 
     @Test
@@ -895,7 +903,7 @@
 
         val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+                .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index d38e97f..40b59c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -45,16 +46,21 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.SurfaceControl;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -70,6 +76,7 @@
 import com.android.wm.shell.util.SplitBounds;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -99,6 +106,11 @@
     private ActivityTaskManager mActivityTaskManager;
     @Mock
     private DisplayInsetsController mDisplayInsetsController;
+    @Mock
+    private IRecentTasksListener mRecentTasksListener;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
@@ -426,6 +438,85 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+    public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
+            throws Exception {
+        mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+        mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+        verify(mRecentTasksListener).onRunningTaskAppeared(taskInfo);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+    public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
+            throws Exception {
+        mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+        mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+        verify(mRecentTasksListener, never()).onRunningTaskAppeared(any());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+    public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
+            throws Exception {
+        mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+        mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+
+        verify(mRecentTasksListener).onRunningTaskChanged(taskInfo);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+    public void
+            taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
+            throws Exception {
+        mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+        mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+
+        verify(mRecentTasksListener, never()).onRunningTaskChanged(any());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+    public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
+            throws Exception {
+        mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+        mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+        verify(mRecentTasksListener).onRunningTaskVanished(taskInfo);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+    public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
+            throws Exception {
+        mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+        mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+        verify(mRecentTasksListener, never()).onRunningTaskVanished(any());
+    }
+
+    @Test
     public void getNullSplitBoundsNonSplitTask() {
         SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
         assertNull("splitBounds should be null for non-split task", sb);
@@ -471,6 +562,7 @@
     private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
         ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
         info.taskId = taskId;
+        info.realActivity = new ComponentName("testPackage", "testClass");
         return info;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
new file mode 100644
index 0000000..82e5a1c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -0,0 +1,340 @@
+/*
+ * 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.wm.shell.windowdecor;
+
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Region;
+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.testing.AndroidTestingRunner;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.window.flags.Flags;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DragResizeWindowGeometry}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragResizeWindowGeometryTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DragResizeWindowGeometryTests {
+    private static final Size TASK_SIZE = new Size(500, 1000);
+    private static final int TASK_CORNER_RADIUS = 10;
+    private static final int EDGE_RESIZE_THICKNESS = 15;
+    private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
+    private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
+    private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
+            TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE,
+            LARGE_CORNER_SIZE);
+    // Points in the edge resize handle. Note that coordinates start from the top left.
+    private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
+            -EDGE_RESIZE_THICKNESS / 2);
+    private static final Point LEFT_EDGE_POINT = new Point(-EDGE_RESIZE_THICKNESS / 2,
+            TASK_SIZE.getHeight() / 2);
+    private static final Point RIGHT_EDGE_POINT = new Point(
+            TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2);
+    private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
+            TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    /**
+     * Check that both groups of objects satisfy equals/hashcode within each group, and that each
+     * group is distinct from the next.
+     */
+    @Test
+    public void testEqualsAndHash() {
+        new EqualsTester()
+                .addEqualityGroup(
+                        GEOMETRY,
+                        new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+                .addEqualityGroup(
+                        new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
+                        new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+                .addEqualityGroup(
+                        new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                LARGE_CORNER_SIZE + 5),
+                        new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                LARGE_CORNER_SIZE + 5))
+                .testEquals();
+    }
+
+    @Test
+    public void testGetTaskSize() {
+        assertThat(GEOMETRY.getTaskSize()).isEqualTo(TASK_SIZE);
+    }
+
+    @Test
+    public void testRegionUnionContainsEdges() {
+        Region region = new Region();
+        GEOMETRY.union(region);
+        assertThat(region.isComplex()).isTrue();
+        // Region excludes task area. Note that coordinates start from top left.
+        assertThat(region.contains(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() / 2)).isFalse();
+        // Region includes edges outside the task window.
+        verifyVerticalEdge(region, LEFT_EDGE_POINT);
+        verifyHorizontalEdge(region, TOP_EDGE_POINT);
+        verifyVerticalEdge(region, RIGHT_EDGE_POINT);
+        verifyHorizontalEdge(region, BOTTOM_EDGE_POINT);
+    }
+
+    private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
+        assertThat(region.contains(point.x, point.y)).isTrue();
+        // Horizontally along the edge is still contained.
+        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+        // Vertically along the edge is not contained.
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+    }
+
+    private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
+        assertThat(region.contains(point.x, point.y)).isTrue();
+        // Horizontally along the edge is not contained.
+        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+        // Vertically along the edge is contained.
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+    }
+
+    /**
+     * Validate that with the flag enabled, the corner resize regions are the largest size, to
+     * capture all eligible input regardless of source (touch or cursor).
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+    public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
+        Region region = new Region();
+        GEOMETRY.union(region);
+        final int cornerRadius = LARGE_CORNER_SIZE / 2;
+
+        new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+    }
+
+    /**
+     * Validate that with the flag disabled, the corner resize regions are the original smaller
+     * size.
+     */
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+    public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
+        Region region = new Region();
+        GEOMETRY.union(region);
+        final int cornerRadius = FINE_CORNER_SIZE / 2;
+
+        new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+    public void testCalculateControlType_edgeDragResizeEnabled_edges() {
+        // The input source (touch or cursor) shouldn't impact the edge resize size.
+        validateCtrlTypeForEdges(/* isTouch= */ false);
+        validateCtrlTypeForEdges(/* isTouch= */ true);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+    public void testCalculateControlType_edgeDragResizeDisabled_edges() {
+        // Edge resizing is not supported when the flag is disabled.
+        validateCtrlTypeForEdges(/* isTouch= */ false);
+        validateCtrlTypeForEdges(/* isTouch= */ false);
+    }
+
+    private void validateCtrlTypeForEdges(boolean isTouch) {
+        assertThat(GEOMETRY.calculateCtrlType(isTouch, LEFT_EDGE_POINT.x,
+                LEFT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_LEFT);
+        assertThat(GEOMETRY.calculateCtrlType(isTouch, TOP_EDGE_POINT.x,
+                TOP_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_TOP);
+        assertThat(GEOMETRY.calculateCtrlType(isTouch, RIGHT_EDGE_POINT.x,
+                RIGHT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_RIGHT);
+        assertThat(GEOMETRY.calculateCtrlType(isTouch, BOTTOM_EDGE_POINT.x,
+                BOTTOM_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_BOTTOM);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+    public void testCalculateControlType_edgeDragResizeEnabled_corners() {
+        final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
+        final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+
+        // When the flag is enabled, points within fine corners should pass regardless of touch or
+        // not. Points outside fine corners should not pass when using a course input (non-touch).
+        fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+        fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, true);
+        fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, true);
+        fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false);
+
+        // When the flag is enabled, points near the large corners should only pass when the point
+        // is within the corner for large touch inputs.
+        largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+        largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true,
+                false);
+        largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+        largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false,
+                false);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+    public void testCalculateControlType_edgeDragResizeDisabled_corners() {
+        final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
+        final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+
+        // When the flag is disabled, points within fine corners should pass only when touch.
+        fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+        fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, false);
+        fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+        fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false);
+
+        // When the flag is disabled, points near the large corners should never pass.
+        largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, false);
+        largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true,
+                false);
+        largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+        largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false,
+                false);
+    }
+
+    /**
+     * Class for creating points for testing the drag resize corners.
+     *
+     * <p>Creates points that are both just within the bounds of each corner, and just outside.
+     */
+    private static final class TestPoints {
+        private final Point mTopLeftPoint;
+        private final Point mTopLeftPointOutside;
+        private final Point mTopRightPoint;
+        private final Point mTopRightPointOutside;
+        private final Point mBottomLeftPoint;
+        private final Point mBottomLeftPointOutside;
+        private final Point mBottomRightPoint;
+        private final Point mBottomRightPointOutside;
+
+        TestPoints(@NonNull Size taskSize, int cornerRadius) {
+            // Point just inside corner square is included.
+            mTopLeftPoint = new Point(-cornerRadius + 1, -cornerRadius + 1);
+            // Point just outside corner square is excluded.
+            mTopLeftPointOutside = new Point(mTopLeftPoint.x - 5, mTopLeftPoint.y - 5);
+
+            mTopRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1, -cornerRadius + 1);
+            mTopRightPointOutside = new Point(mTopRightPoint.x + 5, mTopRightPoint.y - 5);
+
+            mBottomLeftPoint = new Point(-cornerRadius + 1,
+                    taskSize.getHeight() + cornerRadius - 1);
+            mBottomLeftPointOutside = new Point(mBottomLeftPoint.x - 5, mBottomLeftPoint.y + 5);
+
+            mBottomRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1,
+                    taskSize.getHeight() + cornerRadius - 1);
+            mBottomRightPointOutside = new Point(mBottomRightPoint.x + 5, mBottomRightPoint.y + 5);
+        }
+
+        /**
+         * Validates that all test points are either within or without the given region.
+         */
+        public void validateRegion(@NonNull Region region) {
+            // Point just inside corner square is included.
+            assertThat(region.contains(mTopLeftPoint.x, mTopLeftPoint.y)).isTrue();
+            // Point just outside corner square is excluded.
+            assertThat(region.contains(mTopLeftPointOutside.x, mTopLeftPointOutside.y)).isFalse();
+
+            assertThat(region.contains(mTopRightPoint.x, mTopRightPoint.y)).isTrue();
+            assertThat(
+                    region.contains(mTopRightPointOutside.x, mTopRightPointOutside.y)).isFalse();
+
+            assertThat(region.contains(mBottomLeftPoint.x, mBottomLeftPoint.y)).isTrue();
+            assertThat(region.contains(mBottomLeftPointOutside.x,
+                    mBottomLeftPointOutside.y)).isFalse();
+
+            assertThat(region.contains(mBottomRightPoint.x, mBottomRightPoint.y)).isTrue();
+            assertThat(region.contains(mBottomRightPointOutside.x,
+                    mBottomRightPointOutside.y)).isFalse();
+        }
+
+        /**
+         * Validates that all test points within this drag corner size give the correct
+         * {@code @DragPositioningCallback.CtrlType}.
+         */
+        public void validateCtrlTypeForInnerPoints(@NonNull DragResizeWindowGeometry geometry,
+                boolean isTouch, boolean expectedWithinGeometry) {
+            assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPoint.x,
+                    mTopLeftPoint.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+            assertThat(geometry.calculateCtrlType(isTouch, mTopRightPoint.x,
+                    mTopRightPoint.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+            assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPoint.x,
+                    mBottomLeftPoint.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
+                            : CTRL_TYPE_UNDEFINED);
+            assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPoint.x,
+                    mBottomRightPoint.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
+                            : CTRL_TYPE_UNDEFINED);
+        }
+
+        /**
+         * Validates that all test points outside this drag corner size give the correct
+         * {@code @DragPositioningCallback.CtrlType}.
+         */
+        public void validateCtrlTypeForOutsidePoints(@NonNull DragResizeWindowGeometry geometry,
+                boolean isTouch, boolean expectedWithinGeometry) {
+            assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPointOutside.x,
+                    mTopLeftPointOutside.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+            assertThat(geometry.calculateCtrlType(isTouch, mTopRightPointOutside.x,
+                    mTopRightPointOutside.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+            assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPointOutside.x,
+                    mBottomLeftPointOutside.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
+                            : CTRL_TYPE_UNDEFINED);
+            assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPointOutside.x,
+                    mBottomRightPointOutside.y)).isEqualTo(
+                    expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
+                            : CTRL_TYPE_UNDEFINED);
+        }
+    }
+}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 34a6bc2..839c7b6 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -119,30 +119,41 @@
  * appear to be bogus.
  */
 bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32) const
+{
+     return getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, pModWhen,
+      pCrc32, nullptr);
+}
+
+bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
     uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
-    uint32_t* pModWhen, uint32_t* pCrc32) const
+    uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const
 {
     const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
     const ZipEntry& ze = zipEntry->entry;
 
-    if (pMethod != NULL) {
+    if (pMethod != nullptr) {
         *pMethod = ze.method;
     }
-    if (pUncompLen != NULL) {
+    if (pUncompLen != nullptr) {
         *pUncompLen = ze.uncompressed_length;
     }
-    if (pCompLen != NULL) {
+    if (pCompLen != nullptr) {
         *pCompLen = ze.compressed_length;
     }
-    if (pOffset != NULL) {
+    if (pOffset != nullptr) {
         *pOffset = ze.offset;
     }
-    if (pModWhen != NULL) {
+    if (pModWhen != nullptr) {
         *pModWhen = ze.mod_time;
     }
-    if (pCrc32 != NULL) {
+    if (pCrc32 != nullptr) {
         *pCrc32 = ze.crc32;
     }
+    if (pExtraFieldSize != nullptr) {
+        *pExtraFieldSize = ze.extra_field_size;
+    }
 
     return true;
 }
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 031d2e8..f7c5007 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -151,6 +151,10 @@
         uint32_t* pCompLen, off64_t* pOffset, uint32_t* pModWhen,
         uint32_t* pCrc32) const;
 
+    bool getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+        uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+        uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const;
+
     /*
      * Create a new FileMap object that maps a subset of the archive. For
      * an uncompressed entry this effectively provides a pointer to the
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 34932b1..dc669a5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -24,7 +24,6 @@
 #include <SkImageAndroid.h>
 #include <SkImageInfo.h>
 #include <SkMatrix.h>
-#include <SkMultiPictureDocument.h>
 #include <SkOverdrawCanvas.h>
 #include <SkOverdrawColorFilter.h>
 #include <SkPicture.h>
@@ -38,6 +37,7 @@
 #include <android-base/properties.h>
 #include <gui/TraceUtils.h>
 #include <include/android/SkSurfaceAndroid.h>
+#include <include/docs/SkMultiPictureDocument.h>
 #include <include/encode/SkPngEncoder.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <unistd.h>
@@ -185,7 +185,7 @@
         // we need to keep it until after mMultiPic.close()
         // procs is passed as a pointer, but just as a method of having an optional default.
         // procs doesn't need to outlive this Make call.
-        mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs,
+        mMultiPic = SkMultiPictureDocument::Make(mOpenMultiPicStream.get(), &procs,
             [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
             });
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index cf14b1f..823b209 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -18,7 +18,6 @@
 
 #include <SkColorSpace.h>
 #include <SkDocument.h>
-#include <SkMultiPictureDocument.h>
 #include <SkSurface.h>
 
 #include "Lighting.h"
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index b62711f..21fe6ff 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -16,10 +16,10 @@
 
 #include "VkFunctorDrawable.h"
 
-#include <GrBackendDrawableInfo.h>
 #include <SkAndroidFrameworkUtils.h>
 #include <SkImage.h>
 #include <SkM44.h>
+#include <include/gpu/ganesh/vk/GrBackendDrawableInfo.h>
 #include <gui/TraceUtils.h>
 #include <private/hwui/DrawVkInfo.h>
 #include <utils/Color.h>
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 5aa006b..c664d3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -2713,7 +2713,7 @@
 
             List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
             RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-            transfer(targetSession, route, Process.myUserHandle(), mContext.getPackageName());
+            transfer(targetSession, route, mClientUser, mContext.getPackageName());
         }
 
         @Override
diff --git a/media/java/android/media/session/ParcelableListBinder.java b/media/java/android/media/session/ParcelableListBinder.java
index bbf1e08..d788284 100644
--- a/media/java/android/media/session/ParcelableListBinder.java
+++ b/media/java/android/media/session/ParcelableListBinder.java
@@ -45,6 +45,7 @@
     private static final int END_OF_PARCEL = 0;
     private static final int ITEM_CONTINUED = 1;
 
+    private final Class<T> mListElementsClass;
     private final Consumer<List<T>> mConsumer;
 
     private final Object mLock = new Object();
@@ -61,9 +62,11 @@
     /**
      * Creates an instance.
      *
+     * @param listElementsClass the class of the list elements.
      * @param consumer a consumer that consumes the list received
      */
-    public ParcelableListBinder(@NonNull Consumer<List<T>> consumer) {
+    public ParcelableListBinder(Class<T> listElementsClass, @NonNull Consumer<List<T>> consumer) {
+        mListElementsClass = listElementsClass;
         mConsumer = consumer;
     }
 
@@ -83,7 +86,13 @@
                 mCount = data.readInt();
             }
             while (i < mCount && data.readInt() != END_OF_PARCEL) {
-                mList.add(data.readParcelable(null));
+                Object object = data.readParcelable(null);
+                if (mListElementsClass.isAssignableFrom(object.getClass())) {
+                    // Checking list items are of compaitible types to validate against malicious
+                    // apps calling it directly via reflection with non compilable items.
+                    // See b/317048338 for more details
+                    mList.add((T) object);
+                }
                 i++;
             }
             if (i >= mCount) {
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9fd386f..b6b1a45 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,14 +68,6 @@
   <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
   <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
-  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
-  <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
-  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
-  <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
-  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
-  <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
-  <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
-  <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
   <string name="passkey">passkey</string>
   <string name="password">password</string>
   <string name="passkeys">passkeys</string>
@@ -133,6 +125,12 @@
   <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string>
   <!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
+  <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+  <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] -->
+  <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+  <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] -->
+  <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
   <!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index ab70394..694e27a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -21,7 +21,6 @@
 import android.content.Intent
 import android.credentials.selection.BaseDialogResult
 import android.credentials.selection.BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED
-import android.credentials.selection.Constants
 import android.credentials.selection.ProviderPendingIntentResponse
 import android.credentials.selection.UserSelectionDialogResult
 import android.os.Bundle
@@ -117,21 +116,17 @@
         sendCancellationCode(
             cancelCode = cancelCode,
             requestToken = token,
-            resultReceiver = resultReceiver,
-            finalResponseReceiver = finalResponseReceiver
+            resultReceiver = resultReceiver
         )
     }
 
     private fun sendCancellationCode(
         cancelCode: Int,
         requestToken: IBinder?,
-        resultReceiver: ResultReceiver?,
-        finalResponseReceiver: ResultReceiver?
+        resultReceiver: ResultReceiver?
     ) {
         if (requestToken != null && resultReceiver != null) {
-            val resultData = Bundle().apply {
-                putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER, finalResponseReceiver)
-            }
+            val resultData = Bundle()
             BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
             resultReceiver.send(cancelCode, resultData)
         }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 786c441..9242141 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -54,9 +54,3 @@
         Constants.EXTRA_RESULT_RECEIVER,
         ResultReceiver::class.java
     )
-
-val Intent.finalResponseReceiver: ResultReceiver?
-    get() = this.getParcelableExtra(
-        Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
-        ResultReceiver::class.java
-    )
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index 1683cc4..f1f1f7c 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import com.android.credentialmanager.ktx.getCredentialProviderDataList
 import com.android.credentialmanager.ktx.requestInfo
-import com.android.credentialmanager.ktx.finalResponseReceiver
 import com.android.credentialmanager.ktx.resultReceiver
 import com.android.credentialmanager.ktx.toProviderList
 import com.android.credentialmanager.model.Request
@@ -29,7 +28,6 @@
     return Request.Get(
         token = requestInfo?.token,
         resultReceiver = resultReceiver,
-        finalResponseReceiver = finalResponseReceiver,
         providerInfos = getCredentialProviderDataList.toProviderList(context)
     )
 }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index fd99275..cb335fc 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -25,8 +25,7 @@
  */
 sealed class Request private constructor(
     open val token: IBinder?,
-    open val resultReceiver: ResultReceiver? = null,
-    open val finalResponseReceiver: ResultReceiver? = null,
+    open val resultReceiver: ResultReceiver? = null
 ) {
 
     /**
@@ -51,9 +50,8 @@
     data class Get(
         override val token: IBinder?,
         override val resultReceiver: ResultReceiver?,
-        override val finalResponseReceiver: ResultReceiver?,
         val providerInfos: List<ProviderInfo>,
-    ) : Request(token, resultReceiver, finalResponseReceiver)
+    ) : Request(token, resultReceiver)
     /**
      * Request to start the create credentials flow.
      */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index b17a98b..3683235 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -58,7 +58,6 @@
     private val providerEnabledList: List<ProviderData>
     private val providerDisabledList: List<DisabledProviderData>?
     val resultReceiver: ResultReceiver?
-    val finalResponseReceiver: ResultReceiver?
 
     var initialUiState: UiState
 
@@ -105,12 +104,6 @@
             Constants.EXTRA_RESULT_RECEIVER,
             ResultReceiver::class.java
         )
-
-        finalResponseReceiver = intent.getParcelableExtra(
-                Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
-                ResultReceiver::class.java
-        )
-
         isReqForAllOptions = requestInfo?.isShowAllOptionsRequested ?: false
 
         val cancellationRequest = getCancelUiRequest(intent)
@@ -206,7 +199,7 @@
     }
 
     fun onCancel(cancelCode: Int) {
-        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver, finalResponseReceiver)
+        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
     }
 
     fun onOptionSelected(
@@ -226,9 +219,6 @@
         val resultDataBundle = Bundle()
         UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
 
-        resultDataBundle.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
-                finalResponseReceiver)
-
         resultReceiver?.send(
             BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
             resultDataBundle
@@ -296,13 +286,10 @@
         fun sendCancellationCode(
             cancelCode: Int,
             requestToken: IBinder?,
-            resultReceiver: ResultReceiver?,
-            finalResponseReceiver: ResultReceiver?
+            resultReceiver: ResultReceiver?
         ) {
             if (requestToken != null && resultReceiver != null) {
                 val resultData = Bundle()
-                resultData.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
-                        finalResponseReceiver)
 
                 BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
                 resultReceiver.send(cancelCode, resultData)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index ec0da09..a2f55cd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -208,18 +208,13 @@
             android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
             ResultReceiver::class.java
         )
-        val finalResponseResultReceiver = intent.getParcelableExtra(
-                android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
-                ResultReceiver::class.java
-        )
-
         val requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
         )
         CredentialManagerRepo.sendCancellationCode(
             BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
-            requestInfo?.token, resultReceiver, finalResponseResultReceiver
+            requestInfo?.token, resultReceiver
         )
         this.finish()
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 888777e..429bdbf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -30,6 +30,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.common.BiometricError
 import com.android.credentialmanager.common.BiometricFlowType
 import com.android.credentialmanager.common.BiometricPromptState
 import com.android.credentialmanager.common.BiometricResult
@@ -128,13 +129,22 @@
             uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
             val entryIntent = entry.fillInIntent
             entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow)
-            if (biometricState.biometricResult != null) {
+            if (biometricState.biometricResult != null || biometricState.biometricError != null) {
                 if (uiState.isAutoSelectFlow) {
                     Log.w(Constants.LOG_TAG, "Unexpected biometric result exists when " +
                             "autoSelect is preferred.")
                 }
-                entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_TYPE,
-                    biometricState.biometricResult.biometricAuthenticationResult.authenticationType)
+                // TODO(b/333445754) : Decide whether to propagate info on prompt launch
+                if (biometricState.biometricResult != null) {
+                    entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_RESULT,
+                        biometricState.biometricResult.biometricAuthenticationResult
+                            .authenticationType)
+                } else if (biometricState.biometricError != null){
+                    entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_CODE,
+                        biometricState.biometricError.errorCode)
+                    entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_MESSAGE,
+                        biometricState.biometricError.errorMessage)
+                }
             }
             val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent)
                 .setFillInIntent(entryIntent).build()
@@ -219,7 +229,8 @@
     /**************************************************************************/
     fun getFlowOnEntrySelected(
         entry: EntryInfo,
-        authResult: BiometricPrompt.AuthenticationResult? = null
+        authResult: BiometricPrompt.AuthenticationResult? = null,
+        authError: BiometricError? = null,
     ) {
         Log.d(Constants.LOG_TAG, "credential selected: {provider=${entry.providerId}" +
             ", key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
@@ -227,10 +238,11 @@
             uiState.copy(
                 selectedEntry = entry,
                 providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
-                biometricState = if (authResult == null) uiState.biometricState else uiState
+                biometricState = if (authResult == null && authError == null)
+                    uiState.biometricState else if (authResult != null) uiState
                     .biometricState.copy(biometricResult = BiometricResult(
-                            biometricAuthenticationResult = authResult)
-                )
+                            biometricAuthenticationResult = authResult)) else uiState
+                    .biometricState.copy(biometricError = authError)
             )
         } else {
             credManRepo.onOptionSelected(entry.providerId, entry.entryKey, entry.entrySubkey)
@@ -258,6 +270,15 @@
         )
     }
 
+    fun getFlowOnMoreOptionOnlySelected() {
+        Log.d(Constants.LOG_TAG, "More Option Only selected")
+        uiState = uiState.copy(
+                getCredentialUiState = uiState.getCredentialUiState?.copy(
+                        currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
+                )
+        )
+    }
+
     fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) {
         Log.d(Constants.LOG_TAG, "More Option on snackBar selected")
         uiState = uiState.copy(
@@ -350,7 +371,8 @@
 
     fun createFlowOnEntrySelected(
         selectedEntry: EntryInfo,
-        authResult: AuthenticationResult? = null
+        authResult: AuthenticationResult? = null,
+        authError: BiometricError? = null,
     ) {
         val providerId = selectedEntry.providerId
         val entryKey = selectedEntry.entryKey
@@ -362,9 +384,11 @@
             uiState = uiState.copy(
                 selectedEntry = selectedEntry,
                 providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
-                biometricState = if (authResult == null) uiState.biometricState else uiState
+                biometricState = if (authResult == null && authError == null)
+                    uiState.biometricState else if (authResult != null) uiState
                     .biometricState.copy(biometricResult = BiometricResult(
-                        biometricAuthenticationResult = authResult))
+                        biometricAuthenticationResult = authResult)) else uiState
+                    .biometricState.copy(biometricError = authError)
             )
         } else {
             credManRepo.onOptionSelected(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 1253ce3..4109079 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -32,6 +32,7 @@
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
+import android.os.ResultReceiver
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
 import android.service.autofill.Field
@@ -109,17 +110,19 @@
         }
         val sessionId = clientState.getInt(SESSION_ID_KEY)
         val requestId = clientState.getInt(REQUEST_ID_KEY)
+        val resultReceiver = clientState.getParcelable(
+                CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, ResultReceiver::class.java)
         Log.i(TAG, "Autofill sessionId: $sessionId, autofill requestId: $requestId")
-        if (sessionId == 0 || requestId == 0) {
-            Log.i(TAG, "Session Id or request Id not found")
-            callback.onFailure("Session Id or request Id not found")
+        if (sessionId == 0 || requestId == 0 || resultReceiver == null) {
+            Log.i(TAG, "Session Id or request Id or resultReceiver not found")
+            callback.onFailure("Session Id or request Id or resultReceiver not found")
             return
         }
 
         val responseClientState = Bundle()
         responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-                requestId, responseClientState)
+                requestId, resultReceiver, responseClientState)
         // 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,
@@ -531,6 +534,7 @@
             structure: AssistStructure,
             sessionId: Int,
             requestId: Int,
+            resultReceiver: ResultReceiver,
             responseClientState: Bundle
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
@@ -540,6 +544,9 @@
             val dataBundle = Bundle()
             dataBundle.putInt(SESSION_ID_KEY, sessionId)
             dataBundle.putInt(REQUEST_ID_KEY, requestId)
+            dataBundle.putParcelable(CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER,
+                    resultReceiver)
+
             return GetCredentialRequest.Builder(dataBundle)
                     .setCredentialOptions(credentialOptions)
                     .build()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index be3e043..95f49e9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -17,9 +17,12 @@
 package com.android.credentialmanager.common
 
 import android.content.Context
+import android.content.DialogInterface
 import android.graphics.Bitmap
 import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricManager.Authenticators
 import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
 import android.os.CancellationSignal
 import android.util.Log
 import androidx.core.content.ContextCompat.getMainExecutor
@@ -43,19 +46,23 @@
  * Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
  * [providerName], which represents the name of the provider, the [displayTitleText] which is
  * the large text displaying the flow in progress, and the [descriptionForCredential], which
- * describes details of where the credential is being saved, and how.
- * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com:
+ * describes details of where the credential is being saved, and how. [displaySubtitleText] is only expected
+ * to be used by the 'create' flow, optionally, and describes the saved name of the creating entity.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com and
+ * name 'Your', and an rp called 'The App'):
  *
  * 'get' flow:
  *     - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
- *     - [displayTitleText] = "Use your saved passkey for Any Provider?"
- *     - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
- *     Your@Email.com"
+ *     - [displayTitleText] = "Use your saved passkey for The App?"
+ *     - [descriptionForCredential] = "Sign in to The App with your saved passkey for
+ *     Your@gmail.com"
  *
  * 'create' flow:
  *     - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
  *     - [displayTitleText] = "Create passkey to sign in to Any Provider?"
- *     - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ *     - [subtitle] = "Your"
+ *     - [descriptionForCredential] = "You can use your passkey on other devices. It is saved to
+ *  *     Google Password Manager for Your@gmail.com."
  * ).
  *
  * The above are examples; the credential type can change depending on scenario.
@@ -65,8 +72,9 @@
     val providerIcon: Bitmap,
     val providerName: String,
     val displayTitleText: String,
-    val descriptionForCredential: String,
+    val descriptionForCredential: String?,
     val biometricRequestInfo: BiometricRequestInfo,
+    val displaySubtitleText: CharSequence? = null,
 )
 
 /**
@@ -76,6 +84,7 @@
  */
 data class BiometricState(
     val biometricResult: BiometricResult? = null,
+    val biometricError: BiometricError? = null,
     val biometricStatus: BiometricPromptState = BiometricPromptState.INACTIVE
 )
 
@@ -84,7 +93,7 @@
  * so that should this object exist, the result will be retrievable.
  */
 data class BiometricResult(
-    val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult
+    val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult,
 )
 
 /**
@@ -92,16 +101,7 @@
  */
 data class BiometricError(
     val errorCode: Int,
-    val errString: CharSequence? = null
-)
-
-/**
- * Encapsulates the help callback results to easily manage biometric help states in the flow.
- * To specify, this allows us to parse the onAuthenticationHelp method in the [BiometricPrompt].
- */
-data class BiometricHelp(
-    val helpCode: Int,
-    var helpString: CharSequence? = null
+    val errorMessage: CharSequence? = null
 )
 
 /**
@@ -113,7 +113,7 @@
     biometricEntry: EntryInfo,
     context: Context,
     openMoreOptionsPage: () -> Unit,
-    sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+    sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
     onCancelFlowAndFinish: () -> Unit,
     onIllegalStateAndFinish: (String) -> Unit,
     getBiometricPromptState: () -> BiometricPromptState,
@@ -146,7 +146,7 @@
 
     Log.d(TAG, "The BiometricPrompt API call begins.")
     runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
-        onBiometricFailureFallback, BiometricFlowType.GET)
+        onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish)
 }
 
 /**
@@ -158,7 +158,7 @@
     biometricEntry: EntryInfo,
     context: Context,
     openMoreOptionsPage: () -> Unit,
-    sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+    sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
     onCancelFlowAndFinish: () -> Unit,
     onIllegalStateAndFinish: (String) -> Unit,
     getBiometricPromptState: () -> BiometricPromptState,
@@ -190,14 +190,15 @@
 
     Log.d(TAG, "The BiometricPrompt API call begins.")
     runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
-        onBiometricFailureFallback, BiometricFlowType.CREATE)
+        onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish)
 }
 
 /**
  * This will handle the logic for integrating credential manager with the biometric prompt for the
  * single account biometric experience. This simultaneously handles both the get and create flows,
  * by retrieving all the data from credential manager, and properly parsing that data into the
- * biometric prompt.
+ * biometric prompt. It will fallback in cases where the biometric api cannot be called, or when
+ * only device credentials are requested.
  */
 private fun runBiometricFlow(
     context: Context,
@@ -205,28 +206,98 @@
     callback: BiometricPrompt.AuthenticationCallback,
     openMoreOptionsPage: () -> Unit,
     onBiometricFailureFallback: (BiometricFlowType) -> Unit,
-    biometricFlowType: BiometricFlowType
+    biometricFlowType: BiometricFlowType,
+    onCancelFlowAndFinish: () -> Unit
 ) {
-    val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
-        biometricDisplayInfo.biometricRequestInfo, biometricFlowType)
-
-    val cancellationSignal = CancellationSignal()
-    cancellationSignal.setOnCancelListener {
-        Log.d(TAG, "Your cancellation signal was called.")
-        // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
-        // or validate the necessity for this
-    }
-
-    val executor = getMainExecutor(context)
-
     try {
-        biometricPrompt.authenticate(cancellationSignal, executor, callback)
-    } catch (e: IllegalArgumentException) {
+        if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+            onBiometricFailureFallback(biometricFlowType)
+            return
+        }
+
+        val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo,
+            openMoreOptionsPage, biometricDisplayInfo.biometricRequestInfo, onCancelFlowAndFinish)
+
+        val cancellationSignal = CancellationSignal()
+        cancellationSignal.setOnCancelListener {
+            Log.d(TAG, "Your cancellation signal was called.")
+            // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
+            // or validate the necessity for this
+        }
+
+        val executor = getMainExecutor(context)
+
+        val cryptoOpId = getCryptoOpId(biometricDisplayInfo)
+        if (cryptoOpId != null) {
+            biometricPrompt.authenticate(
+                BiometricPrompt.CryptoObject(cryptoOpId.toLong()),
+                cancellationSignal, executor, callback)
+        } else {
+            biometricPrompt.authenticate(cancellationSignal, executor, callback)
+        }
+    } catch (e: Exception) {
+        // TODO(b/334923201) : Specialize exception catching
         Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
         onBiometricFailureFallback(biometricFlowType)
     }
 }
 
+private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
+    return biometricDisplayInfo.biometricRequestInfo.opId
+}
+
+/**
+ * Determines if, given the allowed authenticators, the flow should fallback early. This has
+ * consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks
+ * occur if *only* device credentials are available, to avoid going right into the PIN screen.
+ * Note that if device credential is the only available modality but not requested, or if none
+ * of the requested modalities are available, we propagate the error to the provider instead of
+ * falling back and expect them to handle it as they would prior.
+ * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases
+ */
+private fun onlyUsingDeviceCredentials(
+    biometricDisplayInfo: BiometricDisplayInfo,
+    context: Context
+): Boolean {
+    val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators
+    if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
+        return true
+    }
+
+    val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials(
+        allowedAuthenticators)
+
+    if (!allowedAuthContainsDeviceCredential) {
+        // At this point, allowed authenticators is requesting biometrics without device creds.
+        // Thus, a fallback mechanism will be displayed via our own negative button - "cancel".
+        // Beyond this point, fallbacks will occur if none of the stronger authenticators can
+        // be used.
+        return false
+    }
+
+    val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
+
+    if (allowedAuthContainsDeviceCredential &&
+        biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
+        BiometricManager.BIOMETRIC_SUCCESS &&
+        biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+        BiometricManager.BIOMETRIC_SUCCESS) {
+        return true
+    }
+
+    return false
+}
+
+private fun containsBiometricAuthenticatorWithDeviceCredentials(
+    allowedAuthenticators: Int
+): Boolean {
+    val allowedAuthContainsDeviceCredential = (allowedAuthenticators ==
+            Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL) ||
+            (allowedAuthenticators ==
+                    Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL)
+    return allowedAuthContainsDeviceCredential
+}
+
 /**
  * Sets up the biometric prompt with the UI specific bits.
  * // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject
@@ -236,56 +307,41 @@
     biometricDisplayInfo: BiometricDisplayInfo,
     openMoreOptionsPage: () -> Unit,
     biometricRequestInfo: BiometricRequestInfo,
-    biometricFlowType: BiometricFlowType,
+    onCancelFlowAndFinish: () -> Unit
 ): BiometricPrompt {
-    val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators)
+    val listener =
+        DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> openMoreOptionsPage() }
 
-    val biometricPrompt = BiometricPrompt.Builder(context)
+    val promptContentViewBuilder = PromptContentViewWithMoreOptionsButton.Builder()
+        .setMoreOptionsButtonListener(context.mainExecutor, listener)
+    biometricDisplayInfo.descriptionForCredential?.let {
+        promptContentViewBuilder.setDescription(it) }
+
+    val biometricPromptBuilder = BiometricPrompt.Builder(context)
         .setTitle(biometricDisplayInfo.displayTitleText)
-        // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon
-        .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET)
-            R.string
-                .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
-            getMainExecutor(context)) { _, _ ->
-            openMoreOptionsPage()
-        }
-        .setAllowedAuthenticators(finalAuthenticators)
+        .setAllowedAuthenticators(biometricRequestInfo.allowedAuthenticators)
         .setConfirmationRequired(true)
         .setLogoBitmap(biometricDisplayInfo.providerIcon)
         .setLogoDescription(biometricDisplayInfo.providerName)
-        .setDescription(biometricDisplayInfo.descriptionForCredential)
-        .build()
+        .setContentView(promptContentViewBuilder.build())
 
-    return biometricPrompt
-}
-
-// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button
-// For the time being, we do not support the pin fallback until UX is decided.
-private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
-    var finalAuthenticators = requestAllowedAuthenticators
-
-    if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
-                BiometricManager.Authenticators.BIOMETRIC_WEAK)) {
-        finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
+    if (!containsBiometricAuthenticatorWithDeviceCredentials(biometricDisplayInfo
+            .biometricRequestInfo.allowedAuthenticators)) {
+        biometricPromptBuilder.setNegativeButton(context.getString(R.string.string_cancel),
+            getMainExecutor(context)
+        ) { _: DialogInterface?, _: Int -> onCancelFlowAndFinish() }
     }
 
-    if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
-                BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
-        finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
-    }
+    biometricDisplayInfo.displaySubtitleText?.let { biometricPromptBuilder.setSubtitle(it) }
 
-    if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
-        finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
-    }
-
-    return finalAuthenticators
+    return biometricPromptBuilder.build()
 }
 
 /**
  * Sets up the biometric authentication callback.
  */
 private fun setupBiometricAuthenticationCallback(
-    sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+    sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
     selectedEntry: EntryInfo,
     onCancelFlowAndFinish: () -> Unit,
     onIllegalStateAndFinish: (String) -> Unit,
@@ -301,7 +357,7 @@
                 try {
                     if (authResult != null) {
                         onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
-                        sendDataToProvider(selectedEntry, authResult)
+                        sendDataToProvider(selectedEntry, authResult, /*authError=*/null)
                     } else {
                         onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
                                 "returned a null value.")
@@ -326,8 +382,10 @@
                     // into the selector, parity applies to the selector's cancellation instead
                     // of the provider's biometric prompt cancellation.
                     onCancelFlowAndFinish()
+                } else {
+                    sendDataToProvider(selectedEntry, /*authResult=*/null, /*authError=*/
+                        BiometricError(errorCode, errString))
                 }
-                // TODO(b/333445772) : Propagate to provider
             }
 
             override fun onAuthenticationFailed() {
@@ -414,15 +472,29 @@
     }
     val singleEntryType = selectedEntry.credentialType
     val username = selectedEntry.userName
+
+    // TODO(b/330396140) : Finalize localization and parsing for specific sign in option flows
+    //  (fingerprint, face, etc...))
     displayTitleText = context.getString(
         generateDisplayTitleTextResCode(singleEntryType),
         getRequestDisplayInfo.appName
     )
+
     descriptionText = context.getString(
-        R.string.get_dialog_title_single_tap_for,
+        when (singleEntryType) {
+            CredentialType.PASSKEY ->
+                R.string.get_dialog_description_single_tap_passkey
+
+            CredentialType.PASSWORD ->
+                R.string.get_dialog_description_single_tap_password
+
+            CredentialType.UNKNOWN ->
+                R.string.get_dialog_description_single_tap_saved_sign_in
+        },
         getRequestDisplayInfo.appName,
         username
     )
+
     return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
         displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
         biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
@@ -448,23 +520,12 @@
         getCreateTitleResCode(createRequestDisplayInfo),
         createRequestDisplayInfo.appName
     )
-    val descriptionText: String = context.getString(
-        when (createRequestDisplayInfo.type) {
-            CredentialType.PASSKEY ->
-                R.string.choose_create_single_tap_passkey_title
 
-            CredentialType.PASSWORD ->
-                R.string.choose_create_single_tap_password_title
-
-            CredentialType.UNKNOWN ->
-                R.string.choose_create_single_tap_sign_in_title
-        },
-        createRequestDisplayInfo.appName,
-    )
-    // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas
+    // TODO(b/330396140) : If footerDescription is null, determine if we need to fallback
     return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
-        displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
-        biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
+        displayTitleText = displayTitleText, descriptionForCredential = selectedEntry
+            .footerDescription, biometricRequestInfo = selectedEntry.biometricRequest
+                as BiometricRequestInfo, displaySubtitleText = createRequestDisplayInfo.title)
 }
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index 7e7a74f..3c80113 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -22,7 +22,9 @@
         const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
             "androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
         const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
-        const val BIOMETRIC_AUTH_TYPE = "BIOMETRIC_AUTH_TYPE"
-        const val BIOMETRIC_AUTH_FAILURE = "BIOMETRIC_AUTH_FAILURE"
+        // TODO(b/333445772) : Qualify error codes fully for propagation
+        const val BIOMETRIC_AUTH_RESULT = "BIOMETRIC_AUTH_RESULT"
+        const val BIOMETRIC_AUTH_ERROR_CODE = "BIOMETRIC_AUTH_ERROR_CODE"
+        const val BIOMETRIC_AUTH_ERROR_MESSAGE = "BIOMETRIC_AUTH_ERROR_MESSAGE"
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index d13d86f..149c14a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -349,6 +349,38 @@
     }
 }
 
+@Composable
+fun MoreOptionTopAppBarWithCustomNavigation(
+        text: String,
+        onNavigationIconClicked: () -> Unit,
+        navigationIcon: ImageVector,
+        navigationIconContentDescription: String,
+        bottomPadding: Dp,
+) {
+    Row(
+            modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
+            verticalAlignment = Alignment.CenterVertically,
+    ) {
+        IconButton(
+                modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
+                onClick = onNavigationIconClicked
+        ) {
+            Box(
+                    modifier = Modifier.size(48.dp),
+                    contentAlignment = Alignment.Center,
+            ) {
+                Icon(
+                        imageVector = navigationIcon,
+                        contentDescription = navigationIconContentDescription,
+                        modifier = Modifier.size(24.dp).autoMirrored(),
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+                )
+            }
+        }
+        LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
+    }
+}
+
 private fun Modifier.autoMirrored() = composed {
     when (LocalLayoutDirection.current) {
         LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 122b896..a0915d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,6 +46,7 @@
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricError
 import com.android.credentialmanager.common.BiometricFlowType
 import com.android.credentialmanager.common.BiometricPromptState
 import com.android.credentialmanager.model.EntryInfo
@@ -581,7 +582,11 @@
     onMoreOptionSelected: () -> Unit,
     requestDisplayInfo: RequestDisplayInfo,
     enabledProviderInfo: EnabledProviderInfo,
-    onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+    onBiometricEntrySelected: (
+        EntryInfo,
+        BiometricPrompt.AuthenticationResult?,
+        BiometricError?
+    ) -> Unit,
     onCancelFlowAndFinish: () -> Unit,
     onIllegalScreenStateAndFinish: (String) -> Unit,
     fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 72b7814..e68baf4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.outlined.QrCodeScanner
 import androidx.compose.material3.Divider
 import androidx.compose.material3.TextButton
@@ -52,6 +53,7 @@
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricError
 import com.android.credentialmanager.common.BiometricFlowType
 import com.android.credentialmanager.common.BiometricPromptState
 import com.android.credentialmanager.common.ProviderActivityState
@@ -69,6 +71,7 @@
 import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.ModalBottomSheet
 import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
+import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation
 import com.android.credentialmanager.common.ui.SheetContainerCard
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.common.ui.SnackbarActionText
@@ -147,7 +150,7 @@
                                 .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
                             BiometricSelectionPage(
                                 biometricEntry = getCredentialUiState.activeEntry,
-                                onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+                                onMoreOptionSelected = viewModel::getFlowOnMoreOptionOnlySelected,
                                 onCancelFlowAndFinish = viewModel::onUserCancel,
                                 onIllegalStateAndFinish = viewModel::onIllegalUiState,
                                 requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
@@ -162,6 +165,28 @@
                                 onBiometricPromptStateChange =
                                 viewModel::onBiometricPromptStateChange
                             )
+                        } else if (credmanBiometricApiEnabled() &&
+                                getCredentialUiState.currentScreenState
+                                == GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY) {
+                            AllSignInOptionCard(
+                                    providerInfoList = getCredentialUiState.providerInfoList,
+                                    providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+                                    onEntrySelected = viewModel::getFlowOnEntrySelected,
+                                    onBackButtonClicked = viewModel::onUserCancel,
+                                    onCancel = viewModel::onUserCancel,
+                                    onLog = { viewModel.logUiEvent(it) },
+                                    customTopBar = { MoreOptionTopAppBarWithCustomNavigation(
+                                            text = stringResource(
+                                                    R.string.get_dialog_title_sign_in_options),
+                                            onNavigationIconClicked = viewModel::onUserCancel,
+                                            navigationIcon = Icons.Filled.Close,
+                                            navigationIconContentDescription =
+                                            stringResource(R.string.accessibility_close_button),
+                                            bottomPadding = 0.dp
+                                    ) }
+                            )
+                            viewModel.uiMetrics.log(GetCredentialEvent
+                                    .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS)
                         } else {
                             AllSignInOptionCard(
                                 providerInfoList = getCredentialUiState.providerInfoList,
@@ -223,7 +248,11 @@
     requestDisplayInfo: RequestDisplayInfo,
     providerInfoList: List<ProviderInfo>,
     providerDisplayInfo: ProviderDisplayInfo,
-    onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult?) -> Unit,
+    onBiometricEntrySelected: (
+        EntryInfo,
+        BiometricPrompt.AuthenticationResult?,
+        BiometricError?
+    ) -> Unit,
     fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
     getBiometricPromptState: () -> BiometricPromptState,
     onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
@@ -637,7 +666,13 @@
     return providerId
 }
 
-/** Draws the secondary credential selection page, where all sign-in options are listed. */
+/**
+ * Draws the secondary credential selection page, where all sign-in options are listed.
+ *
+ * By default, this card has 'back' navigation whereby user can navigate back to invoke
+ * [onBackButtonClicked]. However if a different top bar with possibly a different navigation
+ * is required, then the caller of this Composable can set a [customTopBar].
+ */
 @Composable
 fun AllSignInOptionCard(
     providerInfoList: List<ProviderInfo>,
@@ -646,16 +681,21 @@
     onBackButtonClicked: () -> Unit,
     onCancel: () -> Unit,
     onLog: @Composable (UiEventEnum) -> Unit,
+    customTopBar: (@Composable() () -> Unit)? = null
 ) {
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
     val authenticationEntryList = providerDisplayInfo.authenticationEntryList
     SheetContainerCard(topAppBar = {
-        MoreOptionTopAppBar(
-            text = stringResource(R.string.get_dialog_title_sign_in_options),
-            onNavigationIconClicked = onBackButtonClicked,
-            bottomPadding = 0.dp,
-        )
+        if (customTopBar != null) {
+            customTopBar()
+        } else {
+            MoreOptionTopAppBar(
+                    text = stringResource(R.string.get_dialog_title_sign_in_options),
+                    onNavigationIconClicked = onBackButtonClicked,
+                    bottomPadding = 0.dp,
+            )
+        }
     }) {
         var isFirstSection = true
         // For username
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b03407b..8e78861 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -163,7 +163,11 @@
     /** The single tap biometric selection page. */
     BIOMETRIC_SELECTION,
 
-    /** The secondary credential selection page, where all sign-in options are listed. */
+    /**
+     * The secondary credential selection page, where all sign-in options are listed.
+     *
+     * This state is expected to go back to PRIMARY_SELECTION on back navigation
+     */
     ALL_SIGN_IN_OPTIONS,
 
     /** The snackbar only page when there's no account but only a remoteEntry. */
@@ -171,6 +175,14 @@
 
     /** The snackbar when there are only auth entries and all of them turn out to be empty. */
     UNLOCKED_AUTH_ENTRIES_ONLY,
+
+    /**
+     * The secondary credential selection page, where all sign-in options are listed.
+     *
+     * This state has no option for the user to navigate back to PRIMARY_SELECTION, and
+     * instead can be terminated independently.
+     */
+    ALL_SIGN_IN_OPTIONS_ONLY,
 }
 
 
@@ -285,7 +297,7 @@
         providerDisplayInfo.remoteEntry != null)
         GetScreenState.REMOTE_ONLY
     else if (isRequestForAllOptions)
-        GetScreenState.ALL_SIGN_IN_OPTIONS
+        GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
     else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
         GetScreenState.BIOMETRIC_SELECTION
     else GetScreenState.PRIMARY_SELECTION
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
index 6c14563..c6013e2 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -100,7 +100,6 @@
         val getCredentialUiState = Request.Get(
             token = null,
             resultReceiver = null,
-            finalResponseReceiver = null,
             providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
 
         assertThat(getCredentialUiState).isEqualTo(
@@ -113,7 +112,6 @@
         val getCredentialUiState = Request.Get(
             token = null,
             resultReceiver = null,
-            finalResponseReceiver = null,
             providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
                 unknownCredentialEntryInfo)))).toGet(isPrimary = true)
 
@@ -133,7 +131,6 @@
         val getCredentialUiState = Request.Get(
             token = null,
             resultReceiver = null,
-            finalResponseReceiver = null,
             providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
 
         assertThat(getCredentialUiState).isEqualTo(
@@ -152,7 +149,6 @@
         val getCredentialUiState = Request.Get(
             token = null,
             resultReceiver = null,
-            finalResponseReceiver = null,
             providerInfos = listOf(createProviderInfo(credentialList1),
                 createProviderInfo(credentialList2))).toGet(isPrimary = false)
 
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
index b79f34c..cf839f8 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -121,7 +121,6 @@
         stateFlow.value = Request.Get(
             token = null,
             resultReceiver = null,
-            finalResponseReceiver = null,
             providerInfos = emptyList())
 
         mViewModel.back()
@@ -136,7 +135,6 @@
         stateFlow.value = Request.Get(
             token = null,
             resultReceiver = null,
-            finalResponseReceiver = null,
             providerInfos = emptyList())
 
         mViewModel.back()
diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml
index 4428dbb..3cc64a6 100644
--- a/packages/PrintSpooler/res/values-night/themes.xml
+++ b/packages/PrintSpooler/res/values-night/themes.xml
@@ -30,6 +30,7 @@
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
 </resources>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 4dcad10..bd96025 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -31,6 +31,7 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowLightStatusBar">true</item>
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34c60a1..9faebe2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -381,6 +381,14 @@
                 });
     }
 
+    /** Gets devices with matched connection states. */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+        if (mService == null) {
+            return new ArrayList<BluetoothDevice>(0);
+        }
+        return mService.getDevicesMatchingConnectionStates(states);
+    }
+
     public boolean isEnabled(BluetoothDevice device) {
         if (mService == null || device == null) {
             return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index 6fb0179..e91c0bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -168,17 +168,16 @@
     /**
      * Gets a list of preferences that other apps have injected.
      *
-     * @param profileId Identifier of the user/profile to obtain the injected settings for or
-     *                  UserHandle.USER_CURRENT for all profiles associated with current user.
+     * @param profiles UserHandles of the users/profiles for which to obtain the injected settings.
      */
     public Map<Integer, List<Preference>> getInjectedSettings(Context prefContext,
-            final int profileId) {
+            final Set<UserHandle> profiles) {
         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        final List<UserHandle> profiles = um.getUserProfiles();
+        final List<UserHandle> allProfilesForUser = um.getUserProfiles();
         final ArrayMap<Integer, List<Preference>> result = new ArrayMap<>();
         mSettings.clear();
-        for (UserHandle userHandle : profiles) {
-            if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
+        for (UserHandle userHandle : allProfilesForUser) {
+            if (profiles.contains(userHandle)) {
                 final List<Preference> prefs = new ArrayList<>();
                 Iterable<InjectedSetting> settings = getSettings(userHandle);
                 for (InjectedSetting setting : settings) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 9b1e4b7..3e29872 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -52,6 +52,7 @@
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Build;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -131,6 +132,7 @@
     protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
     @NonNull protected final Context mContext;
     @NonNull protected final String mPackageName;
+    @NonNull protected final UserHandle mUserHandle;
     private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
     private MediaDevice mCurrentConnectedDevice;
     private final LocalBluetoothManager mBluetoothManager;
@@ -140,16 +142,19 @@
     /* package */ InfoMediaManager(
             @NonNull Context context,
             @NonNull String packageName,
+            @NonNull UserHandle userHandle,
             @NonNull LocalBluetoothManager localBluetoothManager) {
         mContext = context;
         mBluetoothManager = localBluetoothManager;
         mPackageName = packageName;
+        mUserHandle = userHandle;
     }
 
     /** Creates an instance of InfoMediaManager. */
     public static InfoMediaManager createInstance(
             Context context,
             @Nullable String packageName,
+            @Nullable UserHandle userHandle,
             LocalBluetoothManager localBluetoothManager) {
 
         // The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -159,16 +164,23 @@
             packageName = context.getPackageName();
         }
 
+        if (userHandle == null) {
+            userHandle = android.os.Process.myUserHandle();
+        }
+
         if (Flags.useMediaRouter2ForInfoMediaManager()) {
             try {
-                return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
+                return new RouterInfoMediaManager(
+                        context, packageName, userHandle, localBluetoothManager);
             } catch (PackageNotAvailableException ex) {
                 // TODO: b/293578081 - Propagate this exception to callers for proper handling.
                 Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
-                return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
+                return new NoOpInfoMediaManager(
+                        context, packageName, userHandle, localBluetoothManager);
             }
         } else {
-            return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
+            return new ManagerInfoMediaManager(
+                    context, packageName, userHandle, localBluetoothManager);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 63056b6..0c2414c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,7 +138,10 @@
         }
 
         mInfoMediaManager =
-                InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
+                // TODO: b/321969740 - Take the userHandle as a parameter and pass it through. The
+                // package name is not sufficient to unambiguously identify an app.
+                InfoMediaManager.createInstance(
+                        context, packageName, /* userHandle */ null, mLocalBluetoothManager);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 23063da..d621751 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -21,6 +21,7 @@
 import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -53,8 +54,9 @@
     /* package */ ManagerInfoMediaManager(
             Context context,
             @NonNull String packageName,
+            @NonNull UserHandle userHandle,
             LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, localBluetoothManager);
+        super(context, packageName, userHandle, localBluetoothManager);
 
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
@@ -87,8 +89,7 @@
 
     @Override
     protected void transferToRoute(@NonNull MediaRoute2Info route) {
-        // TODO: b/279555229 - provide real user handle of a caller.
-        mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
+        mRouterManager.transfer(mPackageName, route, mUserHandle);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index cf11c6d..d2b018c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -20,6 +20,7 @@
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -58,8 +59,9 @@
     NoOpInfoMediaManager(
             Context context,
             @NonNull String packageName,
+            @NonNull UserHandle userHandle,
             LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, localBluetoothManager);
+        super(context, packageName, userHandle, localBluetoothManager);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 0dceeba..045c60d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -25,7 +25,7 @@
 import android.media.RouteDiscoveryPreference;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
-import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
@@ -70,15 +70,16 @@
     /* package */ RouterInfoMediaManager(
             Context context,
             @NonNull String packageName,
+            @NonNull UserHandle userHandle,
             LocalBluetoothManager localBluetoothManager)
             throws PackageNotAvailableException {
-        super(context, packageName, localBluetoothManager);
+        super(context, packageName, userHandle, localBluetoothManager);
 
         MediaRouter2 router = null;
 
         if (Flags.enableCrossUserRoutingInMediaRouter2()) {
             try {
-                router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+                router = MediaRouter2.getInstance(context, packageName, userHandle);
             } catch (IllegalArgumentException ex) {
                 // Do nothing
             }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index f0185b9..3bd37a2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,21 +64,23 @@
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
+                InfoMediaManager.createInstance(
+                        mContext, mContext.getPackageName(), mContext.getUser(), null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
-        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
         assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
-        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
@@ -86,7 +88,8 @@
     @RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
+                InfoMediaManager.createInstance(
+                        mContext, mContext.getPackageName(), mContext.getUser(), null);
         assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS
new file mode 100644
index 0000000..384fd9b
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS
@@ -0,0 +1,3 @@
+#Android Media Better Together
+ivanbuper@google.com
+aquilescanta@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index d793867..a4b87da 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -144,7 +144,8 @@
         doReturn(mMediaSessionManager).when(mContext).getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
         mInfoMediaManager =
-                new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
+                new ManagerInfoMediaManager(
+                        mContext, TEST_PACKAGE_NAME, mContext.getUser(), mLocalBluetoothManager);
         mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
         mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
index d630301..908f50d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -46,6 +46,7 @@
                 new NoOpInfoMediaManager(
                         mContext,
                         /* packageName */ "FAKE_PACKAGE_NAME",
+                        mContext.getUser(),
                         /* localBluetoothManager */ null);
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index ad3eb92..e77cf2f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -531,13 +531,22 @@
             pw.println("  put NAMESPACE KEY VALUE [default]");
             pw.println("      Change the contents of KEY to VALUE for the given NAMESPACE.");
             pw.println("      {default} to set as the default value.");
+            pw.println("  override NAMESPACE KEY VALUE");
+            pw.println("      Set flag NAMESPACE/KEY to the given VALUE, and ignores "
+                    + "server-updates for");
+            pw.println("      this flag. This can still be called even if there is no underlying "
+                    + "value set.");
             pw.println("  delete NAMESPACE KEY");
             pw.println("      Delete the entry for KEY for the given NAMESPACE.");
+            pw.println("  clear_override NAMESPACE KEY");
+            pw.println("      Clear local sticky flag override for KEY in the given NAMESPACE.");
             pw.println("  list_namespaces [--public]");
             pw.println("      Prints the name of all (or just the public) namespaces.");
             pw.println("  list [NAMESPACE]");
             pw.println("      Print all keys and values defined, optionally for the given "
                     + "NAMESPACE.");
+            pw.println("  list_local_overrides");
+            pw.println("      Print all flags that have been overridden.");
             pw.println("  reset RESET_MODE [NAMESPACE]");
             pw.println("      Reset all flag values, optionally for a NAMESPACE, according to "
                     + "RESET_MODE.");
@@ -547,8 +556,9 @@
                     + "flags are reset");
             pw.println("  set_sync_disabled_for_tests SYNC_DISABLED_MODE");
             pw.println("      Modifies bulk property setting behavior for tests. When in one of the"
-                    + " disabled modes this ensures that config isn't overwritten.");
-            pw.println("      SYNC_DISABLED_MODE is one of:");
+                    + " disabled modes");
+            pw.println("      this ensures that config isn't overwritten. SYNC_DISABLED_MODE is "
+                    + "one of:");
             pw.println("        none: Sync is not disabled. A reboot may be required to restart"
                     + " syncing.");
             pw.println("        persistent: Sync is disabled, this state will survive a reboot.");
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 40db52e..c88c373 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -117,6 +117,7 @@
         "SystemUILogLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
+        "SystemUI-shared-utils",
         "SystemUI-statsd",
         "SettingsLib",
         "com_android_systemui_flags_lib",
@@ -263,6 +264,7 @@
         "SystemUISharedLib",
         "SystemUICustomizationLib",
         "SystemUICustomizationTestUtils",
+        "SystemUI-shared-utils",
         "SystemUI-statsd",
         "SettingsLib",
         "com_android_systemui_flags_lib",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt
new file mode 100644
index 0000000..c2a2696
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.fold.ui.composable
+
+import androidx.annotation.FloatRange
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.modifiers.padding
+import kotlin.math.roundToInt
+
+/**
+ * Applies a translation that feeds off of the unfold transition that's active while the device is
+ * being folded or unfolded, effectively shifting the element towards the fold hinge.
+ *
+ * @param startSide `true` if the affected element is on the start side (left-hand side in
+ *   left-to-right layouts), `false` otherwise.
+ * @param fullTranslation The maximum translation to apply when the element is the most shifted. The
+ *   modifier will never apply more than this much translation on the element.
+ * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should
+ *   be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model.
+ */
+@Composable
+fun Modifier.unfoldTranslation(
+    startSide: Boolean,
+    fullTranslation: Dp,
+    @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float,
+): Modifier {
+    val translateToTheRight = startSide && LocalLayoutDirection.current == LayoutDirection.Ltr
+    return this.graphicsLayer {
+        translationX =
+            fullTranslation.toPx() *
+                if (translateToTheRight) {
+                    1 - unfoldProgress()
+                } else {
+                    unfoldProgress() - 1
+                }
+    }
+}
+
+/**
+ * Applies horizontal padding that feeds off of the unfold transition that's active while the device
+ * is being folded or unfolded, effectively "squishing" the element on both sides.
+ *
+ * This is horizontal padding so it's applied on both the start and end sides of the element.
+ *
+ * @param fullPadding The maximum padding to apply when the element is the most padded. The modifier
+ *   will never apply more than this much horizontal padding on the element.
+ * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should
+ *   be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model.
+ */
+@Composable
+fun Modifier.unfoldHorizontalPadding(
+    fullPadding: Dp,
+    @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float,
+): Modifier {
+    return this.padding(
+        horizontal = { (fullPadding.toPx() * (1 - unfoldProgress())).roundToInt() },
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 02a12e4..c6c6f57 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -407,15 +407,16 @@
             AndroidView(
                 factory = { context ->
                     ModernShadeCarrierGroupMobileView.constructAndBind(
-                        context = context,
-                        logger = viewModel.mobileIconsViewModel.logger,
-                        slot = "mobile_carrier_shade_group",
-                        viewModel =
-                            (viewModel.mobileIconsViewModel.viewModelForSub(
-                                subId,
-                                StatusBarLocation.SHADE_CARRIER_GROUP
-                            ) as ShadeCarrierGroupMobileIconViewModel),
-                    )
+                            context = context,
+                            logger = viewModel.mobileIconsViewModel.logger,
+                            slot = "mobile_carrier_shade_group",
+                            viewModel =
+                                (viewModel.mobileIconsViewModel.viewModelForSub(
+                                    subId,
+                                    StatusBarLocation.SHADE_CARRIER_GROUP
+                                ) as ShadeCarrierGroupMobileIconViewModel),
+                        )
+                        .also { it.setOnClickListener { viewModel.onShadeCarrierGroupClicked() } }
                 },
             )
         }
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 9bd6f81..01c27a4d 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
@@ -65,6 +65,8 @@
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.fold.ui.composable.unfoldHorizontalPadding
+import com.android.systemui.fold.ui.composable.unfoldTranslation
 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.controller.MediaHierarchyManager
@@ -289,6 +291,7 @@
         remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
     val tileSquishiness by
         animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+    val unfoldTransitionProgress by viewModel.unfoldTransitionProgress.collectAsState()
 
     val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
     val density = LocalDensity.current
@@ -337,10 +340,23 @@
                 modifier =
                     Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
                         .then(brightnessMirrorShowingModifier)
+                        .unfoldHorizontalPadding(
+                            fullPadding = dimensionResource(R.dimen.notification_side_paddings),
+                        ) {
+                            unfoldTransitionProgress
+                        }
             )
 
             Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
-                Box(modifier = Modifier.weight(1f)) {
+                Box(
+                    modifier =
+                        Modifier.weight(1f).unfoldTranslation(
+                            startSide = true,
+                            fullTranslation = dimensionResource(R.dimen.notification_side_paddings),
+                        ) {
+                            unfoldTransitionProgress
+                        },
+                ) {
                     BrightnessMirror(
                         viewModel = viewModel.brightnessMirrorViewModel,
                         qsSceneAdapter = viewModel.qsSceneAdapter,
@@ -407,7 +423,16 @@
                         Modifier.weight(1f)
                             .fillMaxHeight()
                             .padding(bottom = navBarBottomHeight)
-                            .then(brightnessMirrorShowingModifier),
+                            .then(brightnessMirrorShowingModifier)
+                            .unfoldTranslation(
+                                startSide = false,
+                                fullTranslation =
+                                    dimensionResource(
+                                        R.dimen.notification_side_paddings,
+                                    ),
+                            ) {
+                                unfoldTransitionProgress
+                            },
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index fc511e1..e15d315 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -69,9 +69,9 @@
                             role = Role.Button
                             contentDescription = label
                         },
-                    color = MaterialTheme.colorScheme.primaryContainer,
+                    color = MaterialTheme.colorScheme.tertiaryContainer,
                     shape = RoundedCornerShape(28.dp),
-                    contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                    contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
                     onClick = onClick,
                 ) {
                     Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 780e3f2..b2351c4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -66,8 +66,8 @@
                 val colors =
                     if (viewModel.isChecked) {
                         ButtonDefaults.buttonColors(
-                            containerColor = MaterialTheme.colorScheme.primaryContainer,
-                            contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                            containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                            contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
                         )
                     } else {
                         ButtonDefaults.buttonColors(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index c743314..51e2064 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -30,9 +30,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
@@ -119,6 +121,7 @@
             ) {
                 for (itemIndex in items.indices) {
                     val item = items[itemIndex]
+                    val isSelected = itemIndex == scope.selectedIndex
                     Row(
                         modifier =
                             Modifier.height(48.dp)
@@ -126,7 +129,7 @@
                                 .semantics {
                                     item.contentDescription?.let { contentDescription = it }
                                     role = Role.Switch
-                                    selected = itemIndex == scope.selectedIndex
+                                    selected = isSelected
                                 }
                                 .clickable(
                                     interactionSource = null,
@@ -137,7 +140,11 @@
                         verticalAlignment = Alignment.CenterVertically,
                     ) {
                         if (item.icon !== Empty) {
-                            with(items[itemIndex]) { icon() }
+                            CompositionLocalProvider(
+                                LocalContentColor provides colors.getIconColor(isSelected)
+                            ) {
+                                with(items[itemIndex]) { icon() }
+                            }
                         }
                     }
                 }
@@ -163,7 +170,10 @@
                     ) {
                         val item = items[itemIndex]
                         if (item.icon !== Empty) {
-                            with(items[itemIndex]) { label() }
+                            val textColor = colors.getLabelColor(itemIndex == scope.selectedIndex)
+                            CompositionLocalProvider(LocalContentColor provides textColor) {
+                                with(items[itemIndex]) { label() }
+                            }
                         }
                     }
                 }
@@ -265,8 +275,22 @@
     val indicatorColor: Color,
     /** Color of the indicator background. */
     val indicatorBackgroundColor: Color,
+    /** Color of the icon. */
+    val iconColor: Color,
+    /** Color of the icon when it's selected. */
+    val selectedIconColor: Color,
+    /** Color of the label. */
+    val labelColor: Color,
+    /** Color of the label when it's selected. */
+    val selectedLabelColor: Color,
 )
 
+private fun VolumePanelRadioButtonBarColors.getIconColor(selected: Boolean): Color =
+    if (selected) selectedIconColor else iconColor
+
+private fun VolumePanelRadioButtonBarColors.getLabelColor(selected: Boolean): Color =
+    if (selected) selectedLabelColor else labelColor
+
 object VolumePanelRadioButtonBarDefaults {
 
     val DefaultIndicatorBackgroundPadding = 8.dp
@@ -283,12 +307,20 @@
      */
     @Composable
     fun defaultColors(
-        indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+        indicatorColor: Color = MaterialTheme.colorScheme.tertiaryContainer,
         indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+        iconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+        selectedIconColor: Color = MaterialTheme.colorScheme.onTertiaryContainer,
+        labelColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+        selectedLabelColor: Color = MaterialTheme.colorScheme.onSurface,
     ): VolumePanelRadioButtonBarColors =
         VolumePanelRadioButtonBarColors(
             indicatorColor = indicatorColor,
             indicatorBackgroundColor = indicatorBackgroundColor,
+            iconColor = iconColor,
+            selectedIconColor = selectedIconColor,
+            labelColor = labelColor,
+            selectedLabelColor = selectedLabelColor,
         )
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 9a98bde..f377fa6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
 
 import androidx.compose.foundation.basicMarquee
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -29,7 +30,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.toColor
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
@@ -88,18 +88,13 @@
                     isSelected = buttonViewModel.button.isChecked,
                     onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
                     contentDescription = label,
-                    icon = {
-                        Icon(
-                            icon = buttonViewModel.button.icon,
-                            tint = buttonViewModel.iconColor.toColor(),
-                        )
-                    },
+                    icon = { Icon(icon = buttonViewModel.button.icon) },
                     label = {
                         Text(
                             modifier = Modifier.basicMarquee(),
                             text = label,
                             style = MaterialTheme.typography.labelMedium,
-                            color = buttonViewModel.labelColor.toColor(),
+                            color = LocalContentColor.current,
                             textAlign = TextAlign.Center,
                             maxLines = 2
                         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index a54d005..a3467f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -107,7 +107,7 @@
             }
         }
         transition.AnimatedVisibility(
-            visible = { it },
+            visible = { it || !isExpandable },
             enter =
                 expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
             exit =
@@ -122,7 +122,7 @@
                         val sliderState by sliderViewModel.slider.collectAsState()
                         transition.AnimatedVisibility(
                             modifier = Modifier.padding(top = 16.dp),
-                            visible = { it },
+                            visible = { it || !isExpandable },
                             enter = enterTransition(index = index, totalCount = viewModels.size),
                             exit = exitTransition(index = index, totalCount = viewModels.size)
                         ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 228d292..9f5ab3c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -61,7 +61,8 @@
         modifier =
             modifier.clearAndSetSemantics {
                 if (!state.isEnabled) disabled()
-                contentDescription = state.label
+                contentDescription =
+                    state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
 
                 // provide a not animated value to the a11y because it fails to announce the
                 // settled value when it changes rapidly.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 2d4b63e..ae9794a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
@@ -65,8 +66,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.scene.shared.model.FakeSceneDataSource
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -87,7 +86,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth
-import dagger.Lazy
 import java.util.Optional
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -171,7 +169,7 @@
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var deviceEntryInteractor: DeviceEntryInteractor
-    @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
+    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
     private lateinit var fakeSceneDataSource: FakeSceneDataSource
 
@@ -217,9 +215,13 @@
         )
         mSetFlagsRule.disableFlags(
             FLAG_SIDEFPS_CONTROLLER_REFACTOR,
-            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
-            AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
         )
+        if (!com.android.systemui.Flags.sceneContainer()) {
+            mSetFlagsRule.disableFlags(
+                AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+                AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
+            )
+        }
 
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
@@ -268,7 +270,6 @@
                 falsingManager,
                 userSwitcherController,
                 featureFlags,
-                kosmos.sceneContainerFlags,
                 globalSettings,
                 sessionTracker,
                 Optional.of(sideFpsController),
@@ -283,7 +284,7 @@
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
                 keyguardTransitionInteractor,
-                primaryBouncerInteractor,
+                { primaryBouncerInteractor },
             ) {
                 deviceEntryInteractor
             }
@@ -804,17 +805,17 @@
     }
 
     @Test
+    @EnableSceneContainer
     fun dismissesKeyguard_whenSceneChangesToGone() =
         kosmos.testScope.runTest {
-            kosmos.fakeSceneContainerFlags.enabled = true
             // Upon init, we have never dismisses the keyguard.
             underTest.onInit()
             runCurrent()
-            verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor, never())
+                .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
 
             // Once the view is attached, we start listening but simply going to the bouncer scene
-            // is
-            // not enough to trigger a dismissal of the keyguard.
+            // is not enough to trigger a dismissal of the keyguard.
             underTest.onViewAttached()
             fakeSceneDataSource.pause()
             sceneInteractor.changeScene(Scenes.Bouncer, "reason")
@@ -830,7 +831,8 @@
             fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
             runCurrent()
-            verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor, never())
+                .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
 
             // While listening, going from the bouncer scene to the gone scene, does dismiss the
             // keyguard.
@@ -852,11 +854,11 @@
             fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
-            verify(viewMediatorCallback).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
 
             // While listening, moving back to the bouncer scene does not dismiss the keyguard
             // again.
-            clearInvocations(viewMediatorCallback)
+            clearInvocations(primaryBouncerInteractor)
             fakeSceneDataSource.pause()
             sceneInteractor.changeScene(Scenes.Bouncer, "reason")
             sceneTransitionStateFlow.value =
@@ -871,7 +873,8 @@
             fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
             runCurrent()
-            verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor, never())
+                .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
 
             // Detaching the view stops listening, so moving from the bouncer scene to the gone
             // scene
@@ -891,7 +894,8 @@
             fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
-            verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor, never())
+                .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
 
             // While not listening, moving to the lockscreen does not dismiss the keyguard.
             fakeSceneDataSource.pause()
@@ -908,7 +912,8 @@
             fakeSceneDataSource.unpause(expectedScene = Scenes.Lockscreen)
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
             runCurrent()
-            verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor, never())
+                .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
 
             // Reattaching the view starts listening again so moving from the bouncer scene to the
             // gone scene now does dismiss the keyguard again, this time from lockscreen.
@@ -927,7 +932,7 @@
             fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
             runCurrent()
-            verify(viewMediatorCallback).keyguardDone(anyInt())
+            verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 9f52ae9..04c4efb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -45,9 +45,9 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimManager;
 import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
@@ -88,7 +88,7 @@
     FlingAnimationUtils mFlingAnimationUtilsClosing;
 
     @Mock
-    DreamTouchHandler.TouchSession mTouchSession;
+    TouchHandler.TouchSession mTouchSession;
 
     BouncerSwipeTouchHandler mTouchHandler;
 
@@ -258,7 +258,7 @@
     }
 
     private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
-            DreamTouchHandler.TouchSession touchSession,
+            TouchHandler.TouchSession touchSession,
             NotificationShadeWindowController notificationShadeWindowController) {
         touchHandler.onSessionStart(touchSession);
         verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
@@ -677,8 +677,8 @@
     @Test
     public void testTouchSessionOnRemovedCalledTwice() {
         mTouchHandler.onSessionStart(mTouchSession);
-        ArgumentCaptor<DreamTouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
-                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.Callback.class);
+        ArgumentCaptor<TouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
+                ArgumentCaptor.forClass(TouchHandler.TouchSession.Callback.class);
         verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture());
         onRemovedCallbackCaptor.getValue().onRemoved();
         onRemovedCallbackCaptor.getValue().onRemoved();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 6aa821f..27bffd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
 
 import static com.google.common.truth.Truth.assertThat;
@@ -52,7 +52,7 @@
     ShadeViewController mShadeViewController;
 
     @Mock
-    DreamTouchHandler.TouchSession mTouchSession;
+    TouchHandler.TouchSession mTouchSession;
 
     ShadeTouchHandler mTouchHandler;
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
index 7cdd478..099771c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
@@ -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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
index ebbcf98..82de50c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
@@ -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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index caf9219..1cd9d76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.testKosmos
@@ -86,7 +85,6 @@
             AuthenticationRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
                 backgroundDispatcher = kosmos.testDispatcher,
-                flags = kosmos.sceneContainerFlags,
                 clock = clock,
                 getSecurityMode = getSecurityMode,
                 userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 85774c6..60b48f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -571,7 +571,7 @@
                 // THEN the view layout is never updated
                 verify(windowManager, never()).updateViewLayout(any(), any())
 
-                // CLEANUPL we hide to end the job that listens for the finishedGoingToSleep signal
+                // CLEANUP we hide to end the job that listens for the finishedGoingToSleep signal
                 controllerOverlay.hide()
             }
         }
@@ -595,7 +595,7 @@
                 controllerOverlay.updateOverlayParams(overlayParams)
 
                 // THEN the view layout is updated
-                verify(windowManager, never()).updateViewLayout(any(), any())
+                verify(windowManager).updateViewLayout(any(), any())
             }
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 741cde8..d850f17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -29,10 +29,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
@@ -56,6 +56,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class BouncerActionButtonInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@@ -75,7 +76,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        kosmos.fakeSceneContainerFlags.enabled = true
 
         mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index b0d03b1..361b078 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -25,14 +26,15 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
@@ -48,11 +50,13 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class BouncerInteractorTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val authenticationInteractor = kosmos.authenticationInteractor
+    private val uiEventLoggerFake = kosmos.uiEventLoggerFake
 
     private lateinit var underTest: BouncerInteractor
 
@@ -83,6 +87,7 @@
             // Thus, when auth method is sim, we expect to skip here.
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SKIPPED)
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
         }
 
     @Test
@@ -104,6 +109,8 @@
             // Wrong 6-digit pin
             assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
                 .isEqualTo(AuthenticationResult.FAILED)
+            assertThat(uiEventLoggerFake[0].eventId)
+                .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
 
             // Correct input.
             assertThat(
@@ -113,6 +120,9 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
+            assertThat(uiEventLoggerFake[1].eventId)
+                .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
         }
 
     @Test
@@ -148,6 +158,8 @@
             // Wrong input.
             assertThat(underTest.authenticate("alohamora".toList()))
                 .isEqualTo(AuthenticationResult.FAILED)
+            assertThat(uiEventLoggerFake[0].eventId)
+                .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
 
             // Too short input.
             assertThat(
@@ -165,6 +177,9 @@
             // Correct input.
             assertThat(underTest.authenticate("password".toList()))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
+            assertThat(uiEventLoggerFake[1].eventId)
+                .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
         }
 
     @Test
@@ -187,6 +202,8 @@
             assertThat(wrongPattern.size)
                 .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
             assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
+            assertThat(uiEventLoggerFake[0].eventId)
+                .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
 
             // Too short input.
             val tooShortPattern =
@@ -200,6 +217,9 @@
             // Correct input.
             assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
+            assertThat(uiEventLoggerFake[1].eventId)
+                .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 0db0e07..b83c0ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -34,10 +34,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
@@ -60,6 +60,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class BouncerViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -70,7 +71,6 @@
 
     @Before
     fun setUp() {
-        kosmos.fakeSceneContainerFlags.enabled = true
         underTest = kosmos.bouncerViewModel
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 5bb36a0..256687b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -389,6 +389,61 @@
             assertThat(isAnimationEnabled).isTrue()
         }
 
+    @Test
+    fun onPinButtonClicked_whenInputSameLengthAsHintedPin_ignoresClick() =
+        testScope.runTest {
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            assertThat(hintedPinLength).isEqualTo(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+            lockDeviceAndOpenPinBouncer()
+
+            repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH - 1) { repetition ->
+                underTest.onPinButtonClicked(repetition + 1)
+                runCurrent()
+            }
+            kosmos.fakeAuthenticationRepository.pauseCredentialChecking()
+            // If credential checking were not paused, this would check the credentials and succeed.
+            underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+            runCurrent()
+
+            // This one should be ignored because the user has already entered a number of digits
+            // that's equal to the length of the hinting PIN length. It should result in a PIN
+            // that's exactly the same length as the hinting PIN length.
+            underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+            runCurrent()
+
+            assertThat(pin)
+                .isEqualTo(
+                    buildList {
+                        repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH) { index ->
+                            add(index + 1)
+                        }
+                    }
+                )
+
+            kosmos.fakeAuthenticationRepository.unpauseCredentialChecking()
+            runCurrent()
+            assertThat(pin).isEmpty()
+        }
+
+    @Test
+    fun onPinButtonClicked_whenPinNotHinted_doesNotIgnoreClick() =
+        testScope.runTest {
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            assertThat(hintedPinLength).isNull()
+            lockDeviceAndOpenPinBouncer()
+
+            repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) { repetition ->
+                underTest.onPinButtonClicked(repetition + 1)
+                runCurrent()
+            }
+
+            assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+        }
+
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index f21e969..497180b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -61,7 +62,6 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
@@ -698,10 +698,9 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun isCommunalShowing_whenSceneContainerEnabled() =
         testScope.runTest {
-            kosmos.fakeSceneContainerFlags.enabled = true
-
             // Verify default is false
             val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 5caf35b..37a6ac6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
 import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
 import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.fakeSystemPropertiesHelper
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -47,7 +48,6 @@
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -62,6 +62,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class DeviceEntryInteractorTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -74,7 +75,6 @@
 
     @Before
     fun setUp() {
-        kosmos.fakeSceneContainerFlags.enabled = true
         underTest = kosmos.deviceEntryInteractor
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2af6566..41bc1dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -39,10 +39,10 @@
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.complication.ComplicationHostViewController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.statusbar.BlurUtils;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index c143468..4e18a47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -50,11 +50,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchMonitor;
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
 import com.android.systemui.complication.ComplicationLayoutEngine;
 import com.android.systemui.dreams.complication.HideComplicationTouchHandler;
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -127,6 +128,12 @@
     DreamOverlayComponent mDreamOverlayComponent;
 
     @Mock
+    AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
+
+    @Mock
+    AmbientTouchComponent mAmbientTouchComponent;
+
+    @Mock
     DreamOverlayContainerView mDreamOverlayContainerView;
 
     @Mock
@@ -136,7 +143,7 @@
     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @Mock
-    DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+    TouchMonitor mTouchMonitor;
 
     @Mock
     DreamOverlayStateController mStateController;
@@ -166,8 +173,6 @@
                 .thenReturn(mDreamOverlayContainerViewController);
         when(mLifecycleOwner.getRegistry())
                 .thenReturn(mLifecycleRegistry);
-        when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
-                .thenReturn(mDreamOverlayTouchMonitor);
         when(mComplicationComponentFactory
                 .create(any(), any(), any(), any()))
                 .thenReturn(mComplicationComponent);
@@ -179,8 +184,11 @@
                 .create(any(), any()))
                 .thenReturn(mDreamComplicationComponent);
         when(mDreamOverlayComponentFactory
-                .create(any(), any(), any(), any()))
+                .create(any(), any(), any()))
                 .thenReturn(mDreamOverlayComponent);
+        when(mAmbientTouchComponentFactory.create(any(), any())).thenReturn(mAmbientTouchComponent);
+        when(mAmbientTouchComponent.getTouchMonitor())
+                .thenReturn(mTouchMonitor);
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
@@ -193,6 +201,7 @@
                 mComplicationComponentFactory,
                 mDreamComplicationComponentFactory,
                 mDreamOverlayComponentFactory,
+                mAmbientTouchComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
                 mUiEventLogger,
@@ -486,6 +495,7 @@
         assertThat(mService.shouldShowComplications()).isFalse();
 
         clearInvocations(mDreamOverlayComponent);
+        clearInvocations(mAmbientTouchComponent);
         clearInvocations(mWindowManager);
 
         // New dream starting with dream complications showing. Note that when a new dream is
@@ -505,7 +515,7 @@
         // Verify that new instances of overlay container view controller and overlay touch monitor
         // are created.
         verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
-        verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
+        verify(mAmbientTouchComponent).getTouchMonitor();
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
index a434158..8481586 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
@@ -35,9 +35,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.complication.Complication;
 import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.touch.TouchInsetManager;
@@ -74,7 +74,7 @@
     MotionEvent mMotionEvent;
 
     @Mock
-    DreamTouchHandler.TouchSession mSession;
+    TouchHandler.TouchSession mSession;
 
     @Mock
     DreamOverlayStateController mStateController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 8dcf903..29fbee0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -31,6 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -54,7 +55,7 @@
     @Mock
     CentralSurfaces mCentralSurfaces;
     @Mock
-    DreamTouchHandler.TouchSession mTouchSession;
+    TouchHandler.TouchSession mTouchSession;
     CommunalTouchHandler mTouchHandler;
     @Mock
     Lifecycle mLifecycle;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 1dd5d07..12f8918 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -37,8 +38,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -76,7 +75,6 @@
             repository = repository,
             commandQueue = commandQueue,
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
-            sceneContainerFlags = kosmos.sceneContainerFlags,
             bouncerRepository = bouncerRepository,
             configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
             shadeRepository = shadeRepository,
@@ -249,9 +247,9 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun animationDozingTransitions() =
         testScope.runTest {
-            kosmos.fakeSceneContainerFlags.enabled = true
             val isAnimate by collectLastValue(underTest.animateDozingTransitions)
 
             underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 33eb90a..f685058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.MediaTestHelper
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -172,12 +173,147 @@
             val recommendationsLoadingModel =
                 SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
 
-            underTest.setRecommedationsLoadingState(recommendationsLoadingModel)
+            underTest.setRecommendationsLoadingState(recommendationsLoadingModel)
 
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
         }
 
+    @Test
+    fun addMediaControlPlayingThenRemote() =
+        testScope.runTest {
+            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val playingInstanceId = InstanceId.fakeInstanceId(123)
+            val remoteInstanceId = InstanceId.fakeInstanceId(321)
+            val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
+            val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)
+
+            underTest.addSelectedUserMediaEntry(playingData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId))
+            underTest.addSelectedUserMediaEntry(remoteData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))
+
+            assertThat(sortedMedia?.size).isEqualTo(2)
+            assertThat(sortedMedia?.values)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(playingInstanceId),
+                    MediaCommonModel.MediaControl(remoteInstanceId)
+                )
+        }
+
+    @Test
+    fun switchMediaControlsPlaying() =
+        testScope.runTest {
+            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val playingInstanceId1 = InstanceId.fakeInstanceId(123)
+            val playingInstanceId2 = InstanceId.fakeInstanceId(321)
+            var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1)
+            var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2)
+
+            underTest.addSelectedUserMediaEntry(playingData1)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+            underTest.addSelectedUserMediaEntry(playingData2)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+            assertThat(sortedMedia?.size).isEqualTo(2)
+            assertThat(sortedMedia?.values)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(playingInstanceId1),
+                    MediaCommonModel.MediaControl(playingInstanceId2)
+                )
+                .inOrder()
+
+            playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1)
+            playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2)
+
+            underTest.addSelectedUserMediaEntry(playingData1)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+            underTest.addSelectedUserMediaEntry(playingData2)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+            assertThat(sortedMedia?.size).isEqualTo(2)
+            assertThat(sortedMedia?.values)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(playingInstanceId2),
+                    MediaCommonModel.MediaControl(playingInstanceId1)
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun fullOrderTest() =
+        testScope.runTest {
+            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val instanceId1 = InstanceId.fakeInstanceId(123)
+            val instanceId2 = InstanceId.fakeInstanceId(456)
+            val instanceId3 = InstanceId.fakeInstanceId(321)
+            val instanceId4 = InstanceId.fakeInstanceId(654)
+            val instanceId5 = InstanceId.fakeInstanceId(124)
+            val playingAndLocalData = createMediaData("app1", true, LOCAL, false, instanceId1)
+            val playingAndRemoteData = createMediaData("app2", true, REMOTE, false, instanceId2)
+            val stoppedAndLocalData = createMediaData("app3", false, LOCAL, false, instanceId3)
+            val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
+            val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)
+
+            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+            val mediaRecommendations =
+                SmartspaceMediaData(
+                    targetId = KEY_MEDIA_SMARTSPACE,
+                    isActive = true,
+                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
+                )
+
+            underTest.addSelectedUserMediaEntry(stoppedAndLocalData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3))
+
+            underTest.addSelectedUserMediaEntry(stoppedAndRemoteData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId4))
+
+            underTest.addSelectedUserMediaEntry(canResumeData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId5))
+
+            underTest.addSelectedUserMediaEntry(playingAndLocalData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
+
+            underTest.addSelectedUserMediaEntry(playingAndRemoteData)
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
+
+            underTest.setRecommendation(mediaRecommendations)
+            underTest.setRecommendationsLoadingState(
+                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+            )
+
+            assertThat(sortedMedia?.size).isEqualTo(6)
+            assertThat(sortedMedia?.values)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(instanceId1),
+                    MediaCommonModel.MediaControl(instanceId2),
+                    MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+                    MediaCommonModel.MediaControl(instanceId4),
+                    MediaCommonModel.MediaControl(instanceId3),
+                    MediaCommonModel.MediaControl(instanceId5),
+                )
+                .inOrder()
+        }
+
+    private fun createMediaData(
+        app: String,
+        playing: Boolean,
+        playbackLocation: Int,
+        isResume: Boolean,
+        instanceId: InstanceId,
+    ): MediaData {
+        return MediaData(
+            playbackLocation = playbackLocation,
+            resumption = isResume,
+            notificationKey = "key: $app",
+            isPlaying = playing,
+            instanceId = instanceId
+        )
+    }
+
     companion object {
+        private const val LOCAL = MediaData.PLAYBACK_LOCAL
+        private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
         private const val KEY = "KEY"
         private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index a0a1eb3..c15776e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
 import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -94,22 +95,29 @@
                 collectLastValue(underTest.hasActiveMediaOrRecommendation)
             val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
             val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
+            val sortedMedia by collectLastValue(underTest.sortedMedia)
 
             val userMedia = MediaData(active = false)
             val instanceId = userMedia.instanceId
 
             mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
 
             assertThat(hasActiveMediaOrRecommendation).isFalse()
             assertThat(hasActiveMedia).isFalse()
             assertThat(hasAnyMedia).isTrue()
+            assertThat(sortedMedia).containsExactly(MediaCommonModel.MediaControl(instanceId))
 
             assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
                 .isTrue()
+            mediaFilterRepository.addMediaDataLoadingState(
+                MediaDataLoadingModel.Removed(instanceId)
+            )
 
             assertThat(hasActiveMediaOrRecommendation).isFalse()
             assertThat(hasActiveMedia).isFalse()
             assertThat(hasAnyMedia).isFalse()
+            assertThat(sortedMedia).isEmpty()
         }
 
     @Test
@@ -119,6 +127,7 @@
                 collectLastValue(underTest.hasActiveMediaOrRecommendation)
             val hasAnyMediaOrRecommendation by
                 collectLastValue(underTest.hasAnyMediaOrRecommendation)
+            val sortedMedia by collectLastValue(underTest.sortedMedia)
             kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
 
             val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
@@ -131,14 +140,28 @@
             val userMedia = MediaData(active = false)
 
             mediaFilterRepository.setRecommendation(userMediaRecommendation)
+            mediaFilterRepository.setRecommendationsLoadingState(
+                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+            )
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
             assertThat(hasAnyMediaOrRecommendation).isTrue()
+            assertThat(sortedMedia)
+                .containsExactly(MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE))
 
             mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+            mediaFilterRepository.addMediaDataLoadingState(
+                MediaDataLoadingModel.Loaded(userMedia.instanceId)
+            )
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
             assertThat(hasAnyMediaOrRecommendation).isTrue()
+            assertThat(sortedMedia)
+                .containsExactly(
+                    MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+                    MediaCommonModel.MediaControl(userMedia.instanceId)
+                )
+                .inOrder()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index 1cba185..d9224d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -187,7 +187,12 @@
         underTest.startMediaOutputDialog(expandable, PACKAGE_NAME)
 
         verify(kosmos.mediaOutputDialogManager)
-            .createAndShowWithController(eq(PACKAGE_NAME), eq(true), eq(dialogTransitionController))
+            .createAndShowWithController(
+                eq(PACKAGE_NAME),
+                eq(true),
+                eq(dialogTransitionController),
+                eq(null)
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
new file mode 100644
index 0000000..2e5fde8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.reduceBrightColorsController
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileDataInteractorTest : SysuiTestCase() {
+
+    private val isAvailable = true
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val reduceBrightColorsController = kosmos.reduceBrightColorsController
+    private val underTest: ReduceBrightColorsTileDataInteractor =
+        ReduceBrightColorsTileDataInteractor(
+            testScope.testScheduler,
+            isAvailable,
+            reduceBrightColorsController
+        )
+
+    @Test
+    fun alwaysAvailable() =
+        testScope.runTest {
+            val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+            assertThat(availability).hasSize(1)
+            assertThat(availability.last()).isEqualTo(isAvailable)
+        }
+
+    @Test
+    fun dataMatchesTheRepository() =
+        testScope.runTest {
+            val dataList: List<ReduceBrightColorsTileModel> by
+                collectValues(
+                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+
+            reduceBrightColorsController.isReduceBrightColorsActivated = true
+            runCurrent()
+
+            reduceBrightColorsController.isReduceBrightColorsActivated = false
+            runCurrent()
+
+            assertThat(dataList).hasSize(3)
+            assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
+        }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..6ea5e63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.reduceBrightColorsController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileUserActionInteractorTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val controller = kosmos.reduceBrightColorsController
+
+    private val underTest =
+        ReduceBrightColorsTileUserActionInteractor(
+            inputHandler,
+            controller,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        controller.isReduceBrightColorsActivated = wasEnabled
+
+        underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))
+
+        assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        controller.isReduceBrightColorsActivated = wasEnabled
+
+        underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))
+
+        assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
new file mode 100644
index 0000000..10e9bd6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.qsReduceBrightColorsTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsReduceBrightColorsTileConfig
+
+    private lateinit var mapper: ReduceBrightColorsTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            ReduceBrightColorsTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_extra_dim_icon_on, TestStubDrawable())
+                        addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
+                    }
+                    .resources,
+                context.theme
+            )
+    }
+
+    @Test
+    fun disabledModel() {
+        val inputModel = ReduceBrightColorsTileModel(false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createReduceBrightColorsTileState(
+                QSTileState.ActivationState.INACTIVE,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel() {
+        val inputModel = ReduceBrightColorsTileModel(true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.ACTIVE)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createReduceBrightColorsTileState(
+        activationState: QSTileState.ActivationState,
+    ): QSTileState {
+        val label =
+            context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+        return QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(
+                        if (activationState == QSTileState.ActivationState.ACTIVE)
+                            R.drawable.qs_extra_dim_icon_on
+                        else R.drawable.qs_extra_dim_icon_off
+                    )!!,
+                    null
+                )
+            },
+            label,
+            activationState,
+            context.resources
+                .getStringArray(R.array.tile_states_reduce_brightness)[
+                    if (activationState == QSTileState.ActivationState.ACTIVE) Tile.STATE_ACTIVE
+                    else Tile.STATE_INACTIVE],
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
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 139289a..3727c11 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
@@ -24,8 +24,8 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -34,18 +34,8 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -64,8 +54,6 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
     private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
     private val footerActionsViewModel = mock<FooterActionsViewModel>()
     private val footerActionsViewModelFactory =
@@ -74,45 +62,18 @@
         }
     private val footerActionsController = mock<FooterActionsController>()
 
-    private var mobileIconsViewModel: MobileIconsViewModel =
-        MobileIconsViewModel(
-            logger = mock(),
-            verboseLogger = mock(),
-            interactor = mobileIconsInteractor,
-            airplaneModeInteractor =
-                AirplaneModeInteractor(
-                    FakeAirplaneModeRepository(),
-                    FakeConnectivityRepository(),
-                    FakeMobileConnectionsRepository(),
-                ),
-            constants = mock(),
-            flags,
-            scope = testScope.backgroundScope,
-        )
     private val sceneInteractor = kosmos.sceneInteractor
 
-    private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
-
     private lateinit var underTest: QuickSettingsSceneViewModel
 
     @Before
     fun setUp() {
-        shadeHeaderViewModel =
-            ShadeHeaderViewModel(
-                applicationScope = testScope.backgroundScope,
-                context = context,
-                shadeInteractor = kosmos.shadeInteractor,
-                mobileIconsInteractor = mobileIconsInteractor,
-                mobileIconsViewModel = mobileIconsViewModel,
-                privacyChipInteractor = kosmos.privacyChipInteractor,
-                clockInteractor = kosmos.shadeHeaderClockInteractor,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-            )
+        kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
 
         underTest =
             QuickSettingsSceneViewModel(
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
-                shadeHeaderViewModel = shadeHeaderViewModel,
+                shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 footerActionsViewModelFactory = footerActionsViewModelFactory,
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 93302e3..65fd101 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -69,30 +70,22 @@
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
 import com.android.systemui.testKosmos
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -136,9 +129,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
+@EnableSceneContainer
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -180,25 +174,6 @@
         )
     }
 
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-
-    private var mobileIconsViewModel: MobileIconsViewModel =
-        MobileIconsViewModel(
-            logger = mock(),
-            verboseLogger = mock(),
-            interactor = mobileIconsInteractor,
-            airplaneModeInteractor =
-                AirplaneModeInteractor(
-                    FakeAirplaneModeRepository(),
-                    FakeConnectivityRepository(),
-                    FakeMobileConnectionsRepository(),
-                ),
-            constants = mock(),
-            flags = kosmos.fakeFeatureFlagsClassic,
-            scope = testScope.backgroundScope,
-        )
-
-    private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
     private lateinit var shadeSceneViewModel: ShadeSceneViewModel
 
     private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
@@ -241,23 +216,11 @@
         bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
         bouncerViewModel = kosmos.bouncerViewModel
 
-        shadeHeaderViewModel =
-            ShadeHeaderViewModel(
-                applicationScope = testScope.backgroundScope,
-                context = context,
-                shadeInteractor = kosmos.shadeInteractor,
-                mobileIconsInteractor = mobileIconsInteractor,
-                mobileIconsViewModel = mobileIconsViewModel,
-                privacyChipInteractor = kosmos.privacyChipInteractor,
-                clockInteractor = kosmos.shadeHeaderClockInteractor,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-            )
-
         shadeSceneViewModel =
             ShadeSceneViewModel(
                 applicationScope = testScope.backgroundScope,
                 deviceEntryInteractor = deviceEntryInteractor,
-                shadeHeaderViewModel = shadeHeaderViewModel,
+                shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
@@ -266,6 +229,7 @@
                 footerActionsController = kosmos.footerActionsController,
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
                 sceneInteractor = sceneInteractor,
+                unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
             )
 
         val displayTracker = FakeDisplayTracker(context)
@@ -275,15 +239,15 @@
                 applicationScope = testScope.backgroundScope,
                 sceneInteractor = sceneInteractor,
                 deviceEntryInteractor = deviceEntryInteractor,
+                deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+                bouncerInteractor = bouncerInteractor,
                 keyguardInteractor = keyguardInteractor,
-                flags = kosmos.fakeSceneContainerFlags,
                 sysUiState = sysUiState,
                 displayId = displayTracker.defaultDisplayId,
                 sceneLogger = mock(),
                 falsingCollector = kosmos.falsingCollector,
                 falsingManager = kosmos.falsingManager,
                 powerInteractor = powerInteractor,
-                bouncerInteractor = bouncerInteractor,
                 simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
                 authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
                 windowController = mock(),
@@ -292,7 +256,6 @@
                 headsUpInteractor = kosmos.headsUpNotificationInteractor,
                 occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
                 faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
-                deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
             )
         startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 7f7c24e..8e2eea1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -23,10 +23,10 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -39,9 +39,10 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class SceneContainerRepositoryTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index b179c30..63f4816 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -23,13 +23,13 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 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.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
@@ -45,6 +45,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class SceneInteractorTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -55,7 +56,6 @@
 
     @Before
     fun setUp() {
-        kosmos.fakeSceneContainerFlags.enabled = true
         underTest = kosmos.sceneInteractor
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index d5e43f4..bfe5ef7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -82,7 +81,6 @@
                 headsUpManager,
                 powerInteractor,
                 activeNotificationsInteractor,
-                kosmos.sceneContainerFlags,
                 kosmos::sceneInteractor,
             )
             .apply { setUp(notificationPresenter, notificationsController) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 61adcd2..1472a4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -54,7 +54,6 @@
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -102,7 +101,6 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
     private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
     private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
@@ -124,15 +122,15 @@
                 applicationScope = testScope.backgroundScope,
                 sceneInteractor = sceneInteractor,
                 deviceEntryInteractor = deviceEntryInteractor,
+                deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+                bouncerInteractor = bouncerInteractor,
                 keyguardInteractor = keyguardInteractor,
-                flags = sceneContainerFlags,
                 sysUiState = sysUiState,
                 displayId = Display.DEFAULT_DISPLAY,
                 sceneLogger = mock(),
                 falsingCollector = falsingCollector,
                 falsingManager = kosmos.falsingManager,
                 powerInteractor = powerInteractor,
-                bouncerInteractor = bouncerInteractor,
                 simBouncerInteractor = { kosmos.simBouncerInteractor },
                 authenticationInteractor = { authenticationInteractor },
                 windowController = windowController,
@@ -141,7 +139,6 @@
                 headsUpInteractor = kosmos.headsUpNotificationInteractor,
                 occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
                 faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
-                deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
             )
     }
@@ -1245,7 +1242,6 @@
             "Cannot start on the Gone scene and have the device be locked at the same time."
         }
 
-        sceneContainerFlags.enabled = true
         kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
         authenticationMethod?.let {
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 2938acf..ae5bf07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.Kosmos
 import com.google.common.truth.Truth
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,15 +33,11 @@
     @DisableSceneContainer
     fun isNotEnabled_withoutAconfigFlags() {
         Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
-        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
-        Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(false)
     }
 
     @Test
     @EnableSceneContainer
     fun isEnabled_withAconfigFlags() {
         Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
-        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
-        Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(true)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 7b0127e..427b66b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -23,13 +23,13 @@
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.classifier.fakeFalsingManager
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
@@ -45,6 +45,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class SceneContainerViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -58,7 +59,6 @@
 
     @Before
     fun setUp() {
-        kosmos.fakeSceneContainerFlags.enabled = true
         underTest =
             SceneContainerViewModel(
                 sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index cbbcce9..420418b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -22,6 +22,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -30,7 +31,6 @@
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -52,6 +52,7 @@
 @ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class ShadeControllerSceneImplTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
@@ -64,7 +65,6 @@
     @Before
     fun setup() {
         kosmos.testCase = this
-        kosmos.fakeSceneContainerFlags.enabled = true
         kosmos.fakeFeatureFlagsClassic.apply {
             set(Flags.FULL_SCREEN_USER_SWITCHER, false)
             set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index e759b50..26f342a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -22,9 +22,9 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.recents.utilities.Utilities
 import com.android.systemui.testKosmos
@@ -43,8 +43,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @Ignore("b/328827631")
+@EnableSceneContainer
 class ShadeBackActionInteractorImplTest : SysuiTestCase() {
-    val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val sceneInteractor = kosmos.sceneInteractor
     val underTest = kosmos.shadeBackActionInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 4c573d3..f89f18a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -2,29 +2,18 @@
 
 import android.content.Intent
 import android.provider.AlarmClock
+import android.provider.Settings
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.activityStarter
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argThat
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -40,43 +29,13 @@
 class ShadeHeaderViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
+    private val mobileIconsInteractor = kosmos.fakeMobileIconsInteractor
 
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-
-    private var mobileIconsViewModel: MobileIconsViewModel =
-        MobileIconsViewModel(
-            logger = mock(),
-            verboseLogger = mock(),
-            interactor = mobileIconsInteractor,
-            airplaneModeInteractor =
-                AirplaneModeInteractor(
-                    FakeAirplaneModeRepository(),
-                    FakeConnectivityRepository(),
-                    FakeMobileConnectionsRepository(),
-                ),
-            constants = mock(),
-            flags,
-            scope = testScope.backgroundScope,
-        )
-
-    private lateinit var underTest: ShadeHeaderViewModel
+    private val underTest: ShadeHeaderViewModel = kosmos.shadeHeaderViewModel
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        underTest =
-            ShadeHeaderViewModel(
-                applicationScope = testScope.backgroundScope,
-                context = context,
-                shadeInteractor = kosmos.shadeInteractor,
-                mobileIconsInteractor = mobileIconsInteractor,
-                mobileIconsViewModel = mobileIconsViewModel,
-                privacyChipInteractor = kosmos.privacyChipInteractor,
-                clockInteractor = kosmos.shadeHeaderClockInteractor,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-            )
     }
 
     @Test
@@ -105,6 +64,19 @@
                 )
         }
 
+    @Test
+    fun onShadeCarrierGroupClicked_launchesNetworkSettings() =
+        testScope.runTest {
+            val activityStarter = kosmos.activityStarter
+            underTest.onShadeCarrierGroupClicked()
+
+            verify(activityStarter)
+                .postStartActivityDismissingKeyguard(
+                    argThat(IntentMatcherAction(Settings.ACTION_WIRELESS_SETTINGS)),
+                    anyInt(),
+                )
+        }
+
     companion object {
         private val SUB_1 =
             SubscriptionModel(
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 7a681b3..2727af6 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
@@ -27,8 +27,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
@@ -41,20 +39,13 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.domain.startable.shadeStartable
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.testKosmos
+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
@@ -79,29 +70,8 @@
     private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
     private val shadeRepository by lazy { kosmos.shadeRepository }
 
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-
-    private var mobileIconsViewModel: MobileIconsViewModel =
-        MobileIconsViewModel(
-            logger = mock(),
-            verboseLogger = mock(),
-            interactor = mobileIconsInteractor,
-            airplaneModeInteractor =
-                AirplaneModeInteractor(
-                    FakeAirplaneModeRepository(),
-                    FakeConnectivityRepository(),
-                    FakeMobileConnectionsRepository(),
-                ),
-            constants = mock(),
-            flags,
-            scope = testScope.backgroundScope,
-        )
-
     private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
 
-    private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
-
     private lateinit var underTest: ShadeSceneViewModel
 
     @Mock private lateinit var mediaDataManager: MediaDataManager
@@ -109,23 +79,12 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        shadeHeaderViewModel =
-            ShadeHeaderViewModel(
-                applicationScope = testScope.backgroundScope,
-                context = context,
-                shadeInteractor = kosmos.shadeInteractor,
-                mobileIconsInteractor = mobileIconsInteractor,
-                mobileIconsViewModel = mobileIconsViewModel,
-                privacyChipInteractor = kosmos.privacyChipInteractor,
-                clockInteractor = kosmos.shadeHeaderClockInteractor,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-            )
 
         underTest =
             ShadeSceneViewModel(
                 applicationScope = testScope.backgroundScope,
                 deviceEntryInteractor = deviceEntryInteractor,
-                shadeHeaderViewModel = shadeHeaderViewModel,
+                shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
                 qsSceneAdapter = qsSceneAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
@@ -134,6 +93,7 @@
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
                 footerActionsController = kosmos.footerActionsController,
                 sceneInteractor = kosmos.sceneInteractor,
+                unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
             )
     }
 
@@ -297,4 +257,26 @@
             shadeRepository.setShadeMode(ShadeMode.Split)
             assertThat(shadeMode).isEqualTo(ShadeMode.Split)
         }
+
+    @Test
+    fun unfoldTransitionProgress() =
+        testScope.runTest {
+            val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
+            val progress by collectLastValue(underTest.unfoldTransitionProgress)
+
+            unfoldProvider.onTransitionStarted()
+            assertThat(progress).isEqualTo(1f)
+
+            repeat(10) { repetition ->
+                val transitionProgress = 0.1f * (repetition + 1)
+                unfoldProvider.onTransitionProgress(transitionProgress)
+                assertThat(progress).isEqualTo(transitionProgress)
+            }
+
+            unfoldProvider.onTransitionFinishing()
+            assertThat(progress).isEqualTo(1f)
+
+            unfoldProvider.onTransitionFinished()
+            assertThat(progress).isEqualTo(1f)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index a3cf929..01e1aa59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -23,11 +23,11 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -46,11 +46,11 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
 class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
 
     private val kosmos =
         testKosmos().apply {
-            fakeSceneContainerFlags.enabled = true
             fakeFeatureFlagsClassic.apply {
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 8f7a56d..a023033 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -52,7 +52,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -128,7 +127,7 @@
 
     @Before
     fun setUp() {
-        assertThat(kosmos.sceneContainerFlags.isEnabled()).isEqualTo(SceneContainerFlag.isEnabled)
+        assertThat(SceneContainerFlag.isEnabled).isEqualTo(SceneContainerFlag.isEnabled)
         overrideResource(R.bool.config_use_split_notification_shade, false)
         movementFlow = MutableStateFlow(BurnInModel())
         whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
similarity index 60%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
index 6a801e0..3b4cce4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
@@ -15,42 +15,31 @@
  */
 package com.android.systemui.unfold.domain.interactor
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.async
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class UnfoldTransitionInteractorTest : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class UnfoldTransitionInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val unfoldTransitionProgressProvider = kosmos.fakeUnfoldTransitionProgressProvider
 
-    private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
-    private val unfoldTransitionRepository =
-        UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
-
-    private lateinit var underTest: UnfoldTransitionInteractor
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest = UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
-    }
+    private val underTest: UnfoldTransitionInteractor = kosmos.unfoldTransitionInteractor
 
     @Test
     fun waitForTransitionFinish_noEvents_doesNotComplete() =
@@ -88,4 +77,26 @@
             assertThat(deferred.isCompleted).isFalse()
             deferred.cancel()
         }
+
+    @Test
+    fun unfoldProgress() =
+        testScope.runTest {
+            val progress by collectLastValue(underTest.unfoldProgress)
+            runCurrent()
+
+            unfoldTransitionProgressProvider.onTransitionStarted()
+            assertThat(progress).isEqualTo(1f)
+
+            repeat(10) { repetition ->
+                val transitionProgress = 0.1f * (repetition + 1)
+                unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress)
+                assertThat(progress).isEqualTo(transitionProgress)
+            }
+
+            unfoldTransitionProgressProvider.onTransitionFinishing()
+            assertThat(progress).isEqualTo(1f)
+
+            unfoldTransitionProgressProvider.onTransitionFinished()
+            assertThat(progress).isEqualTo(1f)
+        }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 8e2bd9b..79bf5f1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -267,6 +267,9 @@
 
     /** True if the clock will react to tone changes in the seed color. */
     val isReactiveToTone: Boolean = true,
+
+    /** True if the clock is large frame clock, which will use weather in compose. */
+    val useCustomClockScene: Boolean = false,
 )
 
 /** Render configuration options for a clock face. Modifies the way SystemUI behaves. */
@@ -283,6 +286,9 @@
      * animation will be used (e.g. a simple translation).
      */
     val hasCustomPositionUpdatedAnimation: Boolean = false,
+
+    /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
+    val useCustomClockScene: Boolean = false,
 )
 
 /** Structure for keeping clock-specific settings */
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
new file mode 100644
index 0000000..63600be
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:color="@color/overlay_button_ripple">
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?androidprv:attr/materialColorSecondary"/>
+            <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
similarity index 67%
rename from packages/SystemUI/res/layout/auth_biometric_icon.xml
rename to packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
index b2df63d..bb8cece 100644
--- a/packages/SystemUI/res/layout/auth_biometric_icon.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -14,13 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-
-<com.airbnb.lottie.LottieAnimationView
+<shape
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/biometric_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:contentDescription="@null"
-    android:scaleType="fitXY"/>
\ No newline at end of file
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+    <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
new file mode 100644
index 0000000..a5b44e5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android = "http://schemas.android.com/apk/res/android">
+    <size
+        android:width = "@dimen/overlay_action_chip_margin_start"
+        android:height = "0dp"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
similarity index 67%
copy from packages/SystemUI/res/layout/auth_biometric_icon.xml
copy to packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
index b2df63d..76779f9 100644
--- a/packages/SystemUI/res/layout/auth_biometric_icon.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -14,13 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-
-<com.airbnb.lottie.LottieAnimationView
+<shape
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/biometric_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:contentDescription="@null"
-    android:scaleType="fitXY"/>
\ No newline at end of file
+    android:shape="rectangle">
+    <!-- We don't actually draw anything, just expressing the shape for clipping. -->
+    <solid android:color="#0000"/>
+    <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 43ff7b6..76d10cc 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -256,6 +256,24 @@
                 app:constraint_referenced_ids="pair_new_device_button,bluetooth_auto_on_toggle_info_text" />
 
             <Button
+                android:id="@+id/audio_sharing_button"
+                style="@style/Widget.Dialog.Button.BorderButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="9dp"
+                android:layout_marginBottom="@dimen/dialog_bottom_padding"
+                android:layout_marginEnd="@dimen/dialog_side_padding"
+                android:layout_marginStart="@dimen/dialog_side_padding"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/barrier"
+                app:layout_constraintVertical_bias="1"
+                android:visibility="gone" />
+
+            <Button
                 android:id="@+id/done_button"
                 style="@style/Widget.Dialog.Button"
                 android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 1eb05bf..e3c5a7d 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -36,8 +36,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:background="@android:color/transparent"
-            android:focusable="true"
-            android:accessibilityTraversalBefore="@android:id/edit"
+            android:focusable="false"
+            android:importantForAccessibility="yes"
             android:clipToPadding="false"
             android:clipChildren="false">
 
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index eeb64bd8..6a5b999f 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -20,39 +20,37 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <ImageView
+    <FrameLayout
         android:id="@+id/actions_container_background"
         android:visibility="gone"
-        android:layout_height="0dp"
-        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
         android:elevation="4dp"
-        android:background="@drawable/action_chip_container_background"
-        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        android:background="@drawable/shelf_action_chip_container_background"
+        android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
         android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/actions_container"
-        app:layout_constraintEnd_toEndOf="@+id/actions_container"
-        app:layout_constraintBottom_toTopOf="@id/guideline"/>
-    <HorizontalScrollView
-        android:id="@+id/actions_container"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
-        android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
-        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
-        android:elevation="4dp"
-        android:scrollbars="none"
-        app:layout_constraintHorizontal_bias="0"
-        app:layout_constraintWidth_percent="1.0"
-        app:layout_constraintWidth_max="wrap"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
-        <LinearLayout
-            android:id="@+id/screenshot_actions"
+        app:layout_constraintBottom_toTopOf="@id/guideline"
+        >
+        <HorizontalScrollView
+            android:id="@+id/actions_container"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </HorizontalScrollView>
+            android:layout_height="wrap_content"
+            android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
+            android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
+            android:background="@drawable/shelf_action_container_clipping_shape"
+            android:clipToOutline="true"
+            android:scrollbars="none">
+            <LinearLayout
+                android:id="@+id/screenshot_actions"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:showDividers="middle"
+                android:divider="@drawable/shelf_action_chip_divider"
+                android:animateLayoutChanges="true"
+                />
+        </HorizontalScrollView>
+    </FrameLayout>
     <View
         android:id="@+id/screenshot_preview_border"
         android:layout_width="0dp"
@@ -66,7 +64,7 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@id/screenshot_preview"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
-        app:layout_constraintBottom_toTopOf="@id/actions_container"/>
+        app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
     <ImageView
         android:id="@+id/screenshot_preview"
         android:layout_width="@dimen/overlay_x_scale"
diff --git a/packages/SystemUI/res/layout/shelf_action_chip.xml b/packages/SystemUI/res/layout/shelf_action_chip.xml
new file mode 100644
index 0000000..c7606e4
--- /dev/null
+++ b/packages/SystemUI/res/layout/shelf_action_chip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:theme="@style/FloatingOverlay"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
+    android:gravity="center"
+    android:background="@drawable/shelf_action_chip_background"
+    >
+    <ImageView
+        android:id="@+id/overlay_action_chip_icon"
+        android:tint="?androidprv:attr/materialColorOnSecondary"
+        android:layout_width="@dimen/overlay_action_chip_icon_size"
+        android:layout_height="@dimen/overlay_action_chip_icon_size"/>
+    <TextView
+        android:id="@+id/overlay_action_chip_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:textSize="@dimen/overlay_action_chip_text_size"
+        android:textColor="?androidprv:attr/materialColorOnSecondary"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2fed457..f60f6c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -678,6 +678,8 @@
     <string name="turn_on_bluetooth">Use Bluetooth</string>
     <!-- QuickSettings: Bluetooth dialog device connected default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_connected">Connected</string>
+    <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
     <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_saved">Saved</string>
     <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -690,6 +692,10 @@
     <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
     <!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
     <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
+    <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
+    <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</string>
 
     <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c509356..e8e1cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -90,7 +90,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -134,7 +134,6 @@
     private final UserSwitcherController mUserSwitcherController;
     private final GlobalSettings mGlobalSettings;
     private final FeatureFlags mFeatureFlags;
-    private final SceneContainerFlags mSceneContainerFlags;
     private final SessionTracker mSessionTracker;
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
@@ -456,7 +455,6 @@
             FalsingManager falsingManager,
             UserSwitcherController userSwitcherController,
             FeatureFlags featureFlags,
-            SceneContainerFlags sceneContainerFlags,
             GlobalSettings globalSettings,
             SessionTracker sessionTracker,
             Optional<SideFpsController> sideFpsController,
@@ -491,7 +489,6 @@
         mFalsingManager = falsingManager;
         mUserSwitcherController = userSwitcherController;
         mFeatureFlags = featureFlags;
-        mSceneContainerFlags = sceneContainerFlags;
         mGlobalSettings = globalSettings;
         mSessionTracker = sessionTracker;
         if (SideFpsControllerRefactor.isEnabled()) {
@@ -534,7 +531,7 @@
 
         showPrimarySecurityScreen(false);
 
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             // When the scene framework says that the lockscreen has been dismissed, dismiss the
             // keyguard here, revealing the underlying app or launcher:
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4987724..8c51a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -225,7 +225,7 @@
     protected static final int BIOMETRIC_STATE_STOPPED = 0;
 
     /** Biometric authentication state: Listening. */
-    protected static final int BIOMETRIC_STATE_RUNNING = 1;
+    private static final int BIOMETRIC_STATE_RUNNING = 1;
 
     /**
      * Biometric authentication: Cancelling and waiting for the relevant biometric service to
@@ -1145,6 +1145,7 @@
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, FACE);
         }
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         mLogger.d("onFaceAuthenticated");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1155,12 +1156,6 @@
             }
         }
 
-        // Intentionally update the fingerprint running state after sending the
-        // onBiometricAuthenticated callback to listeners. Updating the fingerprint listening state
-        // can update the state of the device which listeners to the callback may rely on.
-        // For example, the alternate bouncer visibility state or udfps finger down state.
-        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-
         // Only authenticate face once when assistant is visible
         mAssistantVisible = false;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 9b09265..afeb0f8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -189,6 +189,5 @@
             ShadeLockscreenInteractor shadeLockscreenInteractor,
             @Nullable ShadeExpansionStateManager shadeExpansionStateManager,
             BiometricUnlockController biometricUnlockController,
-            View notificationContainer,
-            KeyguardBypassController bypassController);
+            View notificationContainer);
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
index 4e5df35..cf2675b 100644
--- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
@@ -74,7 +74,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -131,7 +131,6 @@
     @NonNull private final KeyguardInteractor mKeyguardInteractor;
     @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate;
     @NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
-    @NonNull private final SceneContainerFlags mSceneContainerFlags;
 
     // Tracks the velocity of a touch to help filter out the touches that move too fast.
     private VelocityTracker mVelocityTracker;
@@ -208,8 +207,7 @@
             @NonNull FeatureFlags featureFlags,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             Context context,
-            Lazy<DeviceEntryInteractor> deviceEntryInteractor,
-            SceneContainerFlags sceneContainerFlags
+            Lazy<DeviceEntryInteractor> deviceEntryInteractor
     ) {
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -236,7 +234,6 @@
         mResources = resources;
         mContext = context;
         mDeviceEntryInteractor = deviceEntryInteractor;
-        mSceneContainerFlags = sceneContainerFlags;
 
         mAccessibilityDelegate = new View.AccessibilityDelegate() {
             private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
@@ -746,7 +743,7 @@
         // play device entry haptic (consistent with UDFPS controller longpress)
         vibrateOnLongPress();
 
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             mDeviceEntryInteractor.get().attemptDeviceEntry();
         } else {
             mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 7e96e48..615363d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -72,6 +72,7 @@
     private boolean mEndAnimationCanceled = false;
     @MagnificationState
     private int mState = STATE_DISABLED;
+    private Runnable mOnAnimationEndRunnable;
 
     WindowMagnificationAnimationController(@UiContext Context context) {
         this(context, newValueAnimator(context.getResources()));
@@ -303,12 +304,7 @@
             return;
         }
 
-        // If the animation is playing backwards, mStartSpec will be the final spec we would
-        // like to reach.
-        AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
-        mController.updateWindowMagnificationInternal(
-                spec.mScale, spec.mCenterX, spec.mCenterY,
-                mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
+        mOnAnimationEndRunnable.run();
 
         if (mState == STATE_DISABLING) {
             mController.deleteWindowMagnification();
@@ -333,6 +329,10 @@
     public void onAnimationRepeat(Animator animation) {
     }
 
+    void setOnAnimationEndRunnable(Runnable runnable) {
+        mOnAnimationEndRunnable = runnable;
+    }
+
     private void sendAnimationCallback(boolean success) {
         if (mAnimationCallback != null) {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a847c3d..9837e36 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -260,6 +260,11 @@
         mContext = context;
         mHandler = handler;
         mAnimationController = animationController;
+        mAnimationController.setOnAnimationEndRunnable(() -> {
+            if (Flags.createWindowlessWindowMagnifier()) {
+                notifySourceBoundsChanged();
+            }
+        });
         mAnimationController.setWindowMagnificationController(this);
         mWindowMagnifierCallback = callback;
         mSysUiState = sysUiState;
@@ -1051,11 +1056,15 @@
 
             // Notify source bounds change when the magnifier is not animating.
             if (!mAnimationController.isAnimating()) {
-                mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+                notifySourceBoundsChanged();
             }
         }
     }
 
+    private void notifySourceBoundsChanged() {
+        mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+    }
+
     /**
      * Updates the position of {@link mSurfaceControlViewHost} and layout params of MirrorView based
      * on the position and size of {@link #mMagnificationFrame}.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 7cb028a..b8ff0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility.qs
 
+import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -40,9 +41,14 @@
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.ui.ReduceBrightColorsTileMapper
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
 import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
@@ -105,6 +111,7 @@
         const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
         const val COLOR_INVERSION_TILE_SPEC = "inversion"
         const val FONT_SCALING_TILE_SPEC = "font_scaling"
+        const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
 
         @Provides
         @IntoMap
@@ -198,5 +205,41 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
+        fun provideReduceBrightColorsTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_extra_dim_icon_on,
+                        labelRes = com.android.internal.R.string.reduce_bright_colors_feature_name,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /**
+         * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
+         * behind a flag.
+         */
+        @Provides
+        @IntoMap
+        @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
+        fun provideReduceBrightColorsTileViewModel(
+            factory: QSTileViewModelFactory.Static<ReduceBrightColorsTileModel>,
+            mapper: ReduceBrightColorsTileMapper,
+            stateInteractor: ReduceBrightColorsTileDataInteractor,
+            userActionInteractor: ReduceBrightColorsTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
new file mode 100644
index 0000000..ea00398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.ambient.dagger
+
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent
+import dagger.Module
+
+@Module(subcomponents = [AmbientTouchComponent::class, InputSessionComponent::class])
+interface AmbientModule {
+    companion object {
+        const val TOUCH_HANDLERS = "touch_handlers"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
index 66d413a..d0f08f5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,12 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
-
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE;
+package com.android.systemui.ambient.touch;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -38,9 +33,10 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Flags;
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -55,7 +51,7 @@
 /**
  * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
  */
-public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+public class BouncerSwipeTouchHandler implements TouchHandler {
     /**
      * An interface for creating ValueAnimators.
      */
@@ -226,12 +222,12 @@
             VelocityTrackerFactory velocityTrackerFactory,
             LockPatternUtils lockPatternUtils,
             UserTracker userTracker,
-            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+            @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
                     FlingAnimationUtils flingAnimationUtils,
-            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+            @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
                     FlingAnimationUtils flingAnimationUtilsClosing,
-            @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
-            @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
+            @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
+            @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
             UiEventLogger uiEventLogger) {
         mCentralSurfaces = centralSurfaces;
         mScrimManager = scrimManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
index cddba04..6a76c87 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.PILFER_ON_GESTURE_CONSUME;
 
 import android.os.Looper;
 import android.view.Choreographer;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index e0bf52e..9ef9938 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -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.
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
-import static com.android.systemui.dreams.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
+import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
 
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -35,7 +35,7 @@
  * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
  * to bring down the shade.
  */
-public class ShadeTouchHandler implements DreamTouchHandler {
+public class ShadeTouchHandler implements TouchHandler {
     private final Optional<CentralSurfaces> mSurfaces;
     private final ShadeViewController mShadeViewController;
     private final int mInitiationHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 1ec0008..190bc15 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -25,24 +25,30 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 /**
- * The {@link DreamTouchHandler} interface provides a way for dream overlay components to observe
+ * The {@link TouchHandler} interface provides a way for dream overlay components to observe
  * touch events and gestures with the ability to intercept the latter. Touch interaction sequences
  * are abstracted as sessions. A session represents the time of first
- * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link DreamTouchHandler}
+ * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link TouchHandler}
  * stopping interception of gestures. If no gesture is intercepted, the session continues
- * indefinitely. {@link DreamTouchHandler} have the ability to create a stack of sessions, which
+ * indefinitely. {@link TouchHandler} have the ability to create a stack of sessions, which
  * allows for motion logic to be captured in modal states.
  */
-public interface DreamTouchHandler {
+public interface TouchHandler {
     /**
-     * A touch session captures the interaction surface of a {@link DreamTouchHandler}. Clients
+     * A touch session captures the interaction surface of a {@link TouchHandler}. Clients
      * register listeners as desired to participate in motion/gesture callbacks.
      */
     interface TouchSession {
         interface Callback {
+            /**
+             * Invoked when the session has been removed.
+             */
             void onRemoved();
         }
 
+        /**
+         * Registers a callback to be notified when there are updates to the {@link TouchSession}.
+         */
         void registerCallback(Callback callback);
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 3b22b31..e7e12ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
@@ -37,10 +37,10 @@
 import androidx.lifecycle.LifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.util.display.DisplayHelper;
 
@@ -58,12 +58,12 @@
 import javax.inject.Inject;
 
 /**
- * {@link DreamOverlayTouchMonitor} is responsible for monitoring touches and gestures over the
+ * {@link TouchMonitor} is responsible for monitoring touches and gestures over the
  * dream overlay and redirecting them to a set of listeners. This monitor is in charge of figuring
  * out when listeners are eligible for receiving touches and filtering the listener pool if
  * touches are consumed.
  */
-public class DreamOverlayTouchMonitor {
+public class TouchMonitor {
     // This executor is used to protect {@code mActiveTouchSessions} from being modified
     // concurrently. Any operation that adds or removes values should use this executor.
     public String TAG = "DreamOverlayTouchMonitor";
@@ -83,11 +83,10 @@
             };
 
 
-
     /**
      * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
      */
-    private ListenableFuture<DreamTouchHandler.TouchSession> push(
+    private ListenableFuture<TouchHandler.TouchSession> push(
             TouchSessionImpl touchSessionImpl) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             mMainExecutor.execute(() -> {
@@ -110,7 +109,7 @@
     /**
      * Removes a {@link TouchSessionImpl} from receiving further updates.
      */
-    private ListenableFuture<DreamTouchHandler.TouchSession> pop(
+    private ListenableFuture<TouchHandler.TouchSession> pop(
             TouchSessionImpl touchSessionImpl) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             mMainExecutor.execute(() -> {
@@ -140,11 +139,11 @@
     }
 
     /**
-     * {@link TouchSessionImpl} implements {@link DreamTouchHandler.TouchSession} for
-     * {@link DreamOverlayTouchMonitor}. It enables the monitor to access the associated listeners
+     * {@link TouchSessionImpl} implements {@link TouchHandler.TouchSession} for
+     * {@link TouchMonitor}. It enables the monitor to access the associated listeners
      * and provides the associated client with access to the monitor.
      */
-    private static class TouchSessionImpl implements DreamTouchHandler.TouchSession {
+    private static class TouchSessionImpl implements TouchHandler.TouchSession {
         private final HashSet<InputChannelCompat.InputEventListener> mEventListeners =
                 new HashSet<>();
         private final HashSet<GestureDetector.OnGestureListener> mGestureListeners =
@@ -152,10 +151,10 @@
         private final HashSet<Callback> mCallbacks = new HashSet<>();
 
         private final TouchSessionImpl mPredecessor;
-        private final DreamOverlayTouchMonitor mTouchMonitor;
+        private final TouchMonitor mTouchMonitor;
         private final Rect mBounds;
 
-        TouchSessionImpl(DreamOverlayTouchMonitor touchMonitor, Rect bounds,
+        TouchSessionImpl(TouchMonitor touchMonitor, Rect bounds,
                 TouchSessionImpl predecessor) {
             mPredecessor = predecessor;
             mTouchMonitor = touchMonitor;
@@ -179,12 +178,12 @@
         }
 
         @Override
-        public ListenableFuture<DreamTouchHandler.TouchSession> push() {
+        public ListenableFuture<TouchHandler.TouchSession> push() {
             return mTouchMonitor.push(this);
         }
 
         @Override
-        public ListenableFuture<DreamTouchHandler.TouchSession> pop() {
+        public ListenableFuture<TouchHandler.TouchSession> pop() {
             return mTouchMonitor.pop(this);
         }
 
@@ -275,10 +274,10 @@
             });
         }
         mCurrentInputSession = mInputSessionFactory.create(
-                "dreamOverlay",
-                mInputEventListener,
-                mOnGestureListener,
-                true)
+                        "dreamOverlay",
+                        mInputEventListener,
+                        mOnGestureListener,
+                        true)
                 .getInputSession();
     }
 
@@ -323,21 +322,21 @@
 
 
     private final HashSet<TouchSessionImpl> mActiveTouchSessions = new HashSet<>();
-    private final Collection<DreamTouchHandler> mHandlers;
+    private final Collection<TouchHandler> mHandlers;
     private final DisplayHelper mDisplayHelper;
 
     private boolean mStopMonitoringPending;
 
     private InputChannelCompat.InputEventListener mInputEventListener =
             new InputChannelCompat.InputEventListener() {
-        @Override
-        public void onInputEvent(InputEvent ev) {
-            // No Active sessions are receiving touches. Create sessions for each listener
-            if (mActiveTouchSessions.isEmpty()) {
-                final HashMap<DreamTouchHandler, DreamTouchHandler.TouchSession> sessionMap =
-                        new HashMap<>();
+                @Override
+                public void onInputEvent(InputEvent ev) {
+                    // No Active sessions are receiving touches. Create sessions for each listener
+                    if (mActiveTouchSessions.isEmpty()) {
+                        final HashMap<TouchHandler, TouchHandler.TouchSession> sessionMap =
+                                new HashMap<>();
 
-                for (DreamTouchHandler handler : mHandlers) {
+                        for (TouchHandler handler : mHandlers) {
                             if (!handler.isEnabled()) {
                                 continue;
                             }
@@ -349,46 +348,49 @@
                                 exclusionRect = getCurrentExclusionRect();
                             }
                             handler.getTouchInitiationRegion(
-                                            maxBounds, initiationRegion, exclusionRect);
+                                    maxBounds, initiationRegion, exclusionRect);
 
-                    if (!initiationRegion.isEmpty()) {
-                        // Initiation regions require a motion event to determine pointer location
-                        // within the region.
-                        if (!(ev instanceof MotionEvent)) {
-                            continue;
+                            if (!initiationRegion.isEmpty()) {
+                                // Initiation regions require a motion event to determine pointer
+                                // location
+                                // within the region.
+                                if (!(ev instanceof MotionEvent)) {
+                                    continue;
+                                }
+
+                                final MotionEvent motionEvent = (MotionEvent) ev;
+
+                                // If the touch event is outside the region, then ignore.
+                                if (!initiationRegion.contains(Math.round(motionEvent.getX()),
+                                        Math.round(motionEvent.getY()))) {
+                                    continue;
+                                }
+                            }
+
+                            final TouchSessionImpl sessionStack = new TouchSessionImpl(
+                                    TouchMonitor.this, maxBounds, null);
+                            mActiveTouchSessions.add(sessionStack);
+                            sessionMap.put(handler, sessionStack);
                         }
 
-                        final MotionEvent motionEvent = (MotionEvent) ev;
-
-                        // If the touch event is outside the region, then ignore.
-                        if (!initiationRegion.contains(Math.round(motionEvent.getX()),
-                                Math.round(motionEvent.getY()))) {
-                            continue;
-                        }
+                        // Informing handlers of new sessions is delayed until we have all
+                        // created so the
+                        // final session is correct.
+                        sessionMap.forEach((dreamTouchHandler, touchSession)
+                                -> dreamTouchHandler.onSessionStart(touchSession));
                     }
 
-                    final TouchSessionImpl sessionStack = new TouchSessionImpl(
-                            DreamOverlayTouchMonitor.this, maxBounds, null);
-                    mActiveTouchSessions.add(sessionStack);
-                    sessionMap.put(handler, sessionStack);
+                    // Find active sessions and invoke on InputEvent.
+                    mActiveTouchSessions.stream()
+                            .map(touchSessionStack -> touchSessionStack.getEventListeners())
+                            .flatMap(Collection::stream)
+                            .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
                 }
 
-                // Informing handlers of new sessions is delayed until we have all created so the
-                // final session is correct.
-                sessionMap.forEach((dreamTouchHandler, touchSession)
-                        -> dreamTouchHandler.onSessionStart(touchSession));
-            }
-
-            // Find active sessions and invoke on InputEvent.
-            mActiveTouchSessions.stream()
-                    .map(touchSessionStack -> touchSessionStack.getEventListeners())
-                    .flatMap(Collection::stream)
-                    .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
-        }
-                    private Rect getCurrentExclusionRect() {
-                        return mExclusionRect;
-                    }
-    };
+                private Rect getCurrentExclusionRect() {
+                    return mExclusionRect;
+                }
+            };
 
     /**
      * The {@link Evaluator} interface allows for callers to inspect a listener from the
@@ -401,71 +403,75 @@
 
     private GestureDetector.OnGestureListener mOnGestureListener =
             new GestureDetector.OnGestureListener() {
-        private boolean evaluate(Evaluator evaluator) {
-            final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
+                private boolean evaluate(Evaluator evaluator) {
+                    final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
 
-            // When a gesture is consumed, it is assumed that all touches for the current session
-            // should be directed only to those TouchSessions until those sessions are popped. All
-            // non-participating sessions are removed from receiving further updates with
-            // {@link DreamOverlayTouchMonitor#isolate}.
-            final boolean eventConsumed = mActiveTouchSessions.stream()
-                    .map(touchSession -> {
-                        boolean consume = touchSession.getGestureListeners()
-                                .stream()
-                                .map(listener -> evaluator.evaluate(listener))
-                                .anyMatch(consumed -> consumed);
+                    // When a gesture is consumed, it is assumed that all touches for the current
+                    // session
+                    // should be directed only to those TouchSessions until those sessions are
+                    // popped. All
+                    // non-participating sessions are removed from receiving further updates with
+                    // {@link DreamOverlayTouchMonitor#isolate}.
+                    final boolean eventConsumed = mActiveTouchSessions.stream()
+                            .map(touchSession -> {
+                                boolean consume = touchSession.getGestureListeners()
+                                        .stream()
+                                        .map(listener -> evaluator.evaluate(listener))
+                                        .anyMatch(consumed -> consumed);
 
-                        if (consume) {
-                            consumingSessions.add(touchSession);
-                        }
-                        return consume;
-                    }).anyMatch(consumed -> consumed);
+                                if (consume) {
+                                    consumingSessions.add(touchSession);
+                                }
+                                return consume;
+                            }).anyMatch(consumed -> consumed);
 
-            if (eventConsumed) {
-                DreamOverlayTouchMonitor.this.isolate(consumingSessions);
-            }
+                    if (eventConsumed) {
+                        TouchMonitor.this.isolate(consumingSessions);
+                    }
 
-            return eventConsumed;
-        }
+                    return eventConsumed;
+                }
 
-        // This method is called for gesture events that cannot be consumed.
-        private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
-            mActiveTouchSessions.stream()
-                    .map(touchSession -> touchSession.getGestureListeners())
-                    .flatMap(Collection::stream)
-                    .forEach(listener -> consumer.accept(listener));
-        }
+                // This method is called for gesture events that cannot be consumed.
+                private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
+                    mActiveTouchSessions.stream()
+                            .map(touchSession -> touchSession.getGestureListeners())
+                            .flatMap(Collection::stream)
+                            .forEach(listener -> consumer.accept(listener));
+                }
 
-        @Override
-        public boolean onDown(MotionEvent e) {
-            return evaluate(listener -> listener.onDown(e));
-        }
+                @Override
+                public boolean onDown(MotionEvent e) {
+                    return evaluate(listener -> listener.onDown(e));
+                }
 
-        @Override
-        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
-        }
+                @Override
+                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                        float velocityY) {
+                    return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
+                }
 
-        @Override
-        public void onLongPress(MotionEvent e) {
-            observe(listener -> listener.onLongPress(e));
-        }
+                @Override
+                public void onLongPress(MotionEvent e) {
+                    observe(listener -> listener.onLongPress(e));
+                }
 
-        @Override
-        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-            return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
-        }
+                @Override
+                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                        float distanceY) {
+                    return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
+                }
 
-        @Override
-        public void onShowPress(MotionEvent e) {
-            observe(listener -> listener.onShowPress(e));
-        }
+                @Override
+                public void onShowPress(MotionEvent e) {
+                    observe(listener -> listener.onShowPress(e));
+                }
 
-        @Override
-        public boolean onSingleTapUp(MotionEvent e) {
-            return evaluate(listener -> listener.onSingleTapUp(e));
-        }
-    };
+                @Override
+                public boolean onSingleTapUp(MotionEvent e) {
+                    return evaluate(listener -> listener.onSingleTapUp(e));
+                }
+            };
 
     private InputSessionComponent.Factory mInputSessionFactory;
     private InputSession mCurrentInputSession;
@@ -474,25 +480,27 @@
 
 
     /**
-     * Designated constructor for {@link DreamOverlayTouchMonitor}
-     * @param executor This executor will be used for maintaining the active listener list to avoid
-     *                 concurrent modification.
-     * @param lifecycle {@link DreamOverlayTouchMonitor} will listen to this lifecycle to determine
-     *                                                  whether touch monitoring should be active.
+     * Designated constructor for {@link TouchMonitor}
+     *
+     * @param executor            This executor will be used for maintaining the active listener
+     *                            list to avoid
+     *                            concurrent modification.
+     * @param lifecycle           {@link TouchMonitor} will listen to this lifecycle to determine
+     *                            whether touch monitoring should be active.
      * @param inputSessionFactory This factory will generate the {@link InputSession} requested by
      *                            the monitor. Each session should be unique and valid when
      *                            returned.
-     * @param handlers This set represents the {@link DreamTouchHandler} instances that will
-     *                 participate in touch handling.
+     * @param handlers            This set represents the {@link TouchHandler} instances that will
+     *                            participate in touch handling.
      */
     @Inject
-    public DreamOverlayTouchMonitor(
+    public TouchMonitor(
             @Main Executor executor,
             @Background Executor backgroundExecutor,
             Lifecycle lifecycle,
             InputSessionComponent.Factory inputSessionFactory,
             DisplayHelper displayHelper,
-            Set<DreamTouchHandler> handlers,
+            Set<TouchHandler> handlers,
             IWindowManager windowManagerService,
             @DisplayId int displayId) {
         mDisplayId = displayId;
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
new file mode 100644
index 0000000..390e53b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.ambient.touch.dagger
+
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule.Companion.TOUCH_HANDLERS
+import com.android.systemui.ambient.touch.TouchHandler
+import com.android.systemui.ambient.touch.TouchMonitor
+import dagger.BindsInstance
+import dagger.Subcomponent
+import javax.inject.Named
+
+/**
+ * {@link AmbientTouchComponent} can be used for setting up a touch environment over the entire
+ * display surface. This allows for implementing behaviors such as swiping up to bring up the
+ * bouncer.
+ */
+@Subcomponent(modules = [AmbientTouchModule::class, ShadeModule::class, BouncerSwipeModule::class])
+interface AmbientTouchComponent {
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(
+            @BindsInstance lifecycleOwner: LifecycleOwner,
+            @BindsInstance
+            @Named(TOUCH_HANDLERS)
+            touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+        ): AmbientTouchComponent
+    }
+
+    /** Builds a [TouchMonitor] */
+    fun getTouchMonitor(): TouchMonitor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
new file mode 100644
index 0000000..a4924d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.ambient.touch.dagger
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule
+import com.android.systemui.ambient.touch.TouchHandler
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Named
+
+@Module
+interface AmbientTouchModule {
+    companion object {
+        @JvmStatic
+        @Provides
+        fun providesLifecycle(lifecycleOwner: LifecycleOwner): Lifecycle {
+            return lifecycleOwner.lifecycle
+        }
+
+        @Provides
+        @ElementsIntoSet
+        fun providesDreamTouchHandlers(
+            @Named(AmbientModule.TOUCH_HANDLERS)
+            touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+        ): Set<@JvmSuppressWildcards TouchHandler> {
+            return touchHandlers
+        }
+
+        const val INPUT_SESSION_NAME = "INPUT_SESSION_NAME"
+        const val PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
index a5db2ff..dac2d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
 
 import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.util.TypedValue;
 import android.view.VelocityTracker;
 
+import com.android.systemui.ambient.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -66,7 +66,7 @@
      */
     @Provides
     @IntoSet
-    public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+    public static TouchHandler providesBouncerSwipeTouchHandler(
             BouncerSwipeTouchHandler touchHandler) {
         return touchHandler;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
index 0b14521..203fb64 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
 
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.PILFER_ON_GESTURE_CONSUME;
 
 import android.view.GestureDetector;
 
-import com.android.systemui.dreams.touch.InputSession;
+import com.android.systemui.ambient.touch.InputSession;
 import com.android.systemui.shared.system.InputChannelCompat;
 
 import dagger.BindsInstance;
@@ -42,6 +42,7 @@
      */
     @Subcomponent.Factory
     interface Factory {
+        /** */
         InputSessionComponent create(@Named(INPUT_SESSION_NAME) @BindsInstance String name,
                 @BindsInstance InputChannelCompat.InputEventListener inputEventListener,
                 @BindsInstance GestureDetector.OnGestureListener gestureListener,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
index dfab666..99dbdee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
 
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.INPUT_SESSION_NAME;
 
 import android.view.GestureDetector;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
index 0f08d37..bc2f354 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
@@ -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.
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
 
 import android.content.res.Resources;
 
+import com.android.systemui.ambient.touch.ShadeTouchHandler;
+import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.CommunalTouchHandler;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.ShadeTouchHandler;
 import com.android.systemui.res.R;
 
 import dagger.Binds;
@@ -43,23 +42,14 @@
     public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
             "notification_shade_gesture_initiation_height";
 
-    /** Width of swipe gesture edge to show communal hub. */
-    public static final String COMMUNAL_GESTURE_INITIATION_WIDTH =
-            "communal_gesture_initiation_width";
-
     /**
      * Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
      */
     @Binds
     @IntoSet
-    public abstract DreamTouchHandler providesNotificationShadeTouchHandler(
+    public abstract TouchHandler providesNotificationShadeTouchHandler(
             ShadeTouchHandler touchHandler);
 
-    /** Provides {@link CommunalTouchHandler}. */
-    @Binds
-    @IntoSet
-    public abstract DreamTouchHandler bindCommunalTouchHandler(CommunalTouchHandler touchHandler);
-
     /**
      * Provides the height of the gesture area for notification swipe down.
      */
@@ -69,12 +59,4 @@
         return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
     }
 
-    /**
-     * Provides the width of the gesture area for swiping open communal hub.
-     */
-    @Provides
-    @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
-    public static int providesCommunalGestureInitiationWidth(@Main Resources resources) {
-        return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 776b7bd..94c9982 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
 
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
index 01e4d04..c453ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
@@ -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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
 
 import android.os.PowerManager;
 import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
index 61629ef..0054352 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
@@ -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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
 
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
index 0d0dff6..676221d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
@@ -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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
 
-import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
-import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
+import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
+import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
 
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
index 4ad5161..b07029b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
@@ -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.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch.scrim.dagger;
+package com.android.systemui.ambient.touch.scrim.dagger;
 
-import com.android.systemui.dreams.touch.scrim.BouncerScrimController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.BouncerScrimController;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
 
 import dagger.Module;
 import dagger.Provides;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 454ed27..a9f985f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.onSubscriberAdded
@@ -186,7 +186,6 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    flags: SceneContainerFlags,
     private val clock: SystemClock,
     private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
     private val userRepository: UserRepository,
@@ -255,7 +254,7 @@
     override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
 
     init {
-        if (flags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             // Hydrate failedAuthenticationAttempts initially and whenever the selected user
             // changes.
             applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 61d1c71..4a60d19 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -323,7 +323,7 @@
         overlayParams = updatedOverlayParams
         sensorBounds = updatedOverlayParams.sensorBounds
         getTouchOverlay()?.let {
-            if (addViewRunnable != null) {
+            if (addViewRunnable == null) {
                 // Only updateViewLayout if there's no pending view to add to WM.
                 // If there is a pending view, that means the view hasn't been added yet so there's
                 // no need to update any layouts. Instead the correct params will be used when the
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 66b7d7a..d9d3715 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -26,6 +26,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieOnCompositionLoadedListener
 import com.android.settingslib.widget.LottieColorUtils
 import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
@@ -77,6 +78,8 @@
                     }
 
                 launch {
+                    var lottieOnCompositionLoadedListener: LottieOnCompositionLoadedListener? = null
+
                     combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect {
                         (activeAuthType, iconSize) ->
                         // Every time after bp shows, [isIconViewLoaded] is set to false in
@@ -94,10 +97,18 @@
                                  * TODO(b/288175072): May be able to remove this once constraint
                                  *   layout is implemented
                                  */
-                                iconView.removeAllLottieOnCompositionLoadedListener()
-                                iconView.addLottieOnCompositionLoadedListener {
-                                    promptViewModel.setIsIconViewLoaded(true)
+                                if (lottieOnCompositionLoadedListener != null) {
+                                    iconView.removeLottieOnCompositionLoadedListener(
+                                        lottieOnCompositionLoadedListener!!
+                                    )
                                 }
+                                lottieOnCompositionLoadedListener =
+                                    LottieOnCompositionLoadedListener {
+                                        promptViewModel.setIsIconViewLoaded(true)
+                                    }
+                                iconView.addLottieOnCompositionLoadedListener(
+                                    lottieOnCompositionLoadedListener!!
+                                )
                             }
                             AuthType.Face -> {
                                 /**
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 66aeda6..207f7db 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -217,10 +217,13 @@
         mSwitchBroadcast.setText(mContext.getString(
                 R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
         mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
-        changeOutput.setOnClickListener((view) -> {
-            mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
-            dialog.dismiss();
-        });
+        changeOutput.setOnClickListener(
+                (view) -> {
+                    // TODO: b/321969740 - Take the userHandle as a parameter and pass it through.
+                    //  The package name is not sufficient to unambiguously identify an app.
+                    mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null, null);
+                    dialog.dismiss();
+                });
         cancelBtn.setOnClickListener((view) -> {
             if (DEBUG) {
                 Log.d(TAG, "BroadcastDialog dismiss.");
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
new file mode 100644
index 0000000..e44f054
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import androidx.annotation.StringRes
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+internal sealed class AudioSharingButtonState {
+    object Gone : AudioSharingButtonState()
+    data class Visible(@StringRes val resId: Int) : AudioSharingButtonState()
+}
+
+/** Holds business logic for the audio sharing state. */
+@SysUISingleton
+internal class AudioSharingInteractor
+@Inject
+constructor(
+    private val localBluetoothManager: LocalBluetoothManager?,
+    bluetoothStateInteractor: BluetoothStateInteractor,
+    deviceItemInteractor: DeviceItemInteractor,
+    @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+    /** Flow representing the update of AudioSharingButtonState. */
+    internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
+        combine(
+                bluetoothStateInteractor.bluetoothStateUpdate,
+                deviceItemInteractor.deviceItemUpdate
+            ) { bluetoothState, deviceItem ->
+                getButtonState(bluetoothState, deviceItem)
+            }
+            .flowOn(backgroundDispatcher)
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+                initialValue = AudioSharingButtonState.Gone
+            )
+
+    private fun getButtonState(
+        bluetoothState: Boolean,
+        deviceItem: List<DeviceItem>
+    ): AudioSharingButtonState {
+        return when {
+            // Don't show button when bluetooth is off
+            !bluetoothState -> AudioSharingButtonState.Gone
+            // Show sharing audio when broadcasting
+            BluetoothUtils.isBroadcasting(localBluetoothManager) ->
+                AudioSharingButtonState.Visible(
+                    R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+                )
+            // When not broadcasting, don't show button if there's connected source in any device
+            deviceItem.any {
+                BluetoothUtils.hasConnectedBroadcastSource(
+                    it.cachedBluetoothDevice,
+                    localBluetoothManager
+                )
+            } -> AudioSharingButtonState.Gone
+            // Show audio sharing when there's a connected LE audio device
+            deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
+                AudioSharingButtonState.Visible(
+                    R.string.quick_settings_bluetooth_audio_sharing_button
+                )
+            else -> AudioSharingButtonState.Gone
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 94d7af7..17f9e63 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -25,12 +25,17 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
 @SysUISingleton
@@ -40,9 +45,10 @@
     private val localBluetoothManager: LocalBluetoothManager?,
     private val logger: BluetoothTileDialogLogger,
     @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
-    internal val bluetoothStateUpdate: StateFlow<Boolean?> =
+    internal val bluetoothStateUpdate: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val listener =
                     object : BluetoothCallback {
@@ -64,16 +70,22 @@
                 localBluetoothManager?.eventManager?.registerCallback(listener)
                 awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
             }
+            .onStart { emit(isBluetoothEnabled()) }
+            .flowOn(backgroundDispatcher)
             .stateIn(
                 coroutineScope,
                 SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
-                initialValue = null
+                initialValue = false
             )
 
-    internal var isBluetoothEnabled: Boolean
-        get() = localBluetoothManager?.bluetoothAdapter?.isEnabled == true
-        set(value) {
-            if (isBluetoothEnabled != value) {
+    suspend fun isBluetoothEnabled(): Boolean =
+        withContext(backgroundDispatcher) {
+            localBluetoothManager?.bluetoothAdapter?.isEnabled == true
+        }
+
+    suspend fun setBluetoothEnabled(value: Boolean) {
+        withContext(backgroundDispatcher) {
+            if (isBluetoothEnabled() != value) {
                 localBluetoothManager?.bluetoothAdapter?.apply {
                     if (value) enable() else disable()
                     logger.logBluetoothState(
@@ -83,6 +95,7 @@
                 }
             }
         }
+    }
 
     companion object {
         private const val TAG = "BtStateInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index c7d171d..dd8c0df 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -27,6 +27,7 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
 import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.Switch
@@ -59,7 +60,6 @@
 internal constructor(
     @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
     @Assisted private val cachedContentHeight: Int,
-    @Assisted private val bluetoothToggleInitialValue: Boolean,
     @Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
     @Assisted private val dismissListener: Runnable,
     @Main private val mainDispatcher: CoroutineDispatcher,
@@ -69,8 +69,7 @@
     private val systemuiDialogFactory: SystemUIDialog.Factory,
 ) : SystemUIDialog.Delegate {
 
-    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
-        MutableStateFlow(bluetoothToggleInitialValue)
+    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
     internal val bluetoothStateToggle
         get() = mutableBluetoothStateToggle.asStateFlow()
 
@@ -99,7 +98,6 @@
         fun create(
             initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
             cachedContentHeight: Int,
-            bluetoothEnabled: Boolean,
             dialogCallback: BluetoothTileDialogCallback,
             dimissListener: Runnable
         ): BluetoothTileDialogDelegate
@@ -130,6 +128,9 @@
         getPairNewDeviceButton(dialog).setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
+        getAudioSharingButtonView(dialog).setOnClickListener {
+            bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
+        }
         getScrollViewContent(dialog).apply {
             minimumHeight =
                 resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
@@ -211,9 +212,19 @@
         getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
     }
 
+    internal fun onAudioSharingButtonUpdated(
+        dialog: SystemUIDialog,
+        visibility: Int,
+        label: String?
+    ) {
+        getAudioSharingButtonView(dialog).apply {
+            this.visibility = visibility
+            label?.let { text = it }
+        }
+    }
+
     private fun setupToggle(dialog: SystemUIDialog) {
         val toggleView = getToggleView(dialog)
-        toggleView.isChecked = bluetoothToggleInitialValue
         toggleView.setOnCheckedChangeListener { view, isChecked ->
             mutableBluetoothStateToggle.value = isChecked
             view.apply {
@@ -259,6 +270,10 @@
         return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
     }
 
+    private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
+        return dialog.requireViewById(R.id.audio_sharing_button)
+    }
+
     private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
         return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
     }
@@ -412,6 +427,8 @@
         const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
             "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+        const val ACTION_AUDIO_SHARING =
+            "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
         const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index add1647..b592b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -30,9 +30,12 @@
     @UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
     @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
     @UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
+    @UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
     @UiEvent(doc = "Connected other device clicked to disconnect")
     CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
-    @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617);
+    @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
+    @UiEvent(doc = "The audio sharing button is clicked")
+    BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700);
 
     override fun getId() = metricId
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index e65b657..eb919e3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -28,9 +28,11 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.systemui.Prefs
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
@@ -61,6 +63,7 @@
     private val deviceItemInteractor: DeviceItemInteractor,
     private val bluetoothStateInteractor: BluetoothStateInteractor,
     private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
+    private val audioSharingInteractor: AudioSharingInteractor,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
     private val uiEventLogger: UiEventLogger,
@@ -119,7 +122,8 @@
                                     dialog,
                                     it.take(MAX_DEVICE_ITEM_ENTRY),
                                     showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
-                                    showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+                                    showPairNewDevice =
+                                        bluetoothStateInteractor.isBluetoothEnabled()
                                 )
                                 animateProgressBar(dialog, false)
                             }
@@ -142,10 +146,25 @@
                     }
                     .launchIn(this)
 
+                if (BluetoothUtils.isAudioSharingEnabled()) {
+                    audioSharingInteractor.audioSharingButtonStateUpdate
+                        .onEach {
+                            if (it is AudioSharingButtonState.Visible) {
+                                dialogDelegate.onAudioSharingButtonUpdated(
+                                    dialog,
+                                    VISIBLE,
+                                    context.getString(it.resId)
+                                )
+                            } else {
+                                dialogDelegate.onAudioSharingButtonUpdated(dialog, GONE, null)
+                            }
+                        }
+                        .launchIn(this)
+                }
+
                 // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
                 // the device item list.
                 bluetoothStateInteractor.bluetoothStateUpdate
-                    .filterNotNull()
                     .onEach {
                         dialogDelegate.onBluetoothStateUpdated(
                             dialog,
@@ -165,9 +184,10 @@
                 // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
                 // send the new value to the bluetoothStateInteractor and animate the progress bar.
                 dialogDelegate.bluetoothStateToggle
+                    .filterNotNull()
                     .onEach {
                         dialogDelegate.animateProgressBar(dialog, true)
-                        bluetoothStateInteractor.isBluetoothEnabled = it
+                        bluetoothStateInteractor.setBluetoothEnabled(it)
                     }
                     .launchIn(this)
 
@@ -222,11 +242,10 @@
 
         return bluetoothDialogDelegateFactory.create(
             UiProperties.build(
-                bluetoothStateInteractor.isBluetoothEnabled,
+                bluetoothStateInteractor.isBluetoothEnabled(),
                 isAutoOnToggleFeatureAvailable()
             ),
             cachedContentHeight,
-            bluetoothStateInteractor.isBluetoothEnabled,
             this@BluetoothTileDialogViewModel,
             { cancelJob() }
         )
@@ -256,6 +275,11 @@
         startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
     }
 
+    override fun onAudioSharingButtonClicked(view: View) {
+        uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
+        startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+    }
+
     private fun cancelJob() {
         job?.cancel()
         job = null
@@ -312,4 +336,5 @@
     fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
     fun onSeeAllClicked(view: View)
     fun onPairNewDeviceClicked(view: View)
+    fun onAudioSharingButtonClicked(view: View)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index dc5efef..0ea98d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -37,6 +37,7 @@
 
 enum class DeviceItemType {
     ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+    AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
     AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
     CONNECTED_BLUETOOTH_DEVICE,
     SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index f04ba75..49d0847 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -21,13 +21,16 @@
 import android.media.AudioManager
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.flags.Flags
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
 import com.android.systemui.res.R
 
 private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
 private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
 private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
 private val connected = R.string.quick_settings_bluetooth_device_connected
+private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
 private val saved = R.string.quick_settings_bluetooth_device_saved
 private val actionAccessibilityLabelActivate =
     R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -39,35 +42,81 @@
     abstract fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager,
     ): Boolean
 
     abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
+
+    companion object {
+        @JvmStatic
+        fun createDeviceItem(
+            context: Context,
+            cachedDevice: CachedBluetoothDevice,
+            type: DeviceItemType,
+            connectionSummary: String,
+            background: Int,
+            actionAccessibilityLabel: String
+        ): DeviceItem {
+            return DeviceItem(
+                type = type,
+                cachedBluetoothDevice = cachedDevice,
+                deviceName = cachedDevice.name,
+                connectionSummary = connectionSummary,
+                iconWithDescription =
+                    BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
+                        Pair(it.first, it.second)
+                    },
+                background = background,
+                isEnabled = !cachedDevice.isBusy,
+                actionAccessibilityLabel = actionAccessibilityLabel
+            )
+        }
+    }
 }
 
 internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary ?: "",
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = backgroundOn,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary ?: "",
+            backgroundOn,
+            context.getString(actionAccessibilityLabelDisconnect)
+        )
+    }
+}
+
+internal class AudioSharingMediaDeviceItemFactory(
+    private val localBluetoothManager: LocalBluetoothManager?
+) : DeviceItemFactory() {
+    override fun isFilterMatched(
+        context: Context,
+        cachedDevice: CachedBluetoothDevice,
+        audioManager: AudioManager
+    ): Boolean {
+        return enableLeAudioSharing() &&
+            BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
+    }
+
+    override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(audioSharing),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
+            ""
         )
     }
 }
@@ -76,7 +125,7 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -87,27 +136,21 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
     }
 
-    // TODO(b/298124674): move create() to the abstract class to reduce duplicate code
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
-                    ?: context.getString(connected),
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(connected),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            context.getString(actionAccessibilityLabelActivate)
         )
     }
 }
@@ -116,7 +159,7 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -127,32 +170,25 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
-            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
-                context,
-                cachedDevice.getDevice()
-            ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+                BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
         } else {
             BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
         }
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
-                    ?: context.getString(connected),
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(connected),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            context.getString(actionAccessibilityLabelDisconnect)
         )
     }
 }
@@ -161,32 +197,26 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
-            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
-                context,
-                cachedDevice.getDevice()
-            ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+                cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+                !cachedDevice.isConnected
         } else {
             cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
         }
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
-                    ?: context.getString(saved),
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(saved),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            context.getString(actionAccessibilityLabelActivate)
         )
     }
 }
@@ -195,7 +225,7 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
             !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 4e28caf..66e593b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -113,6 +113,7 @@
     private var deviceItemFactoryList: List<DeviceItemFactory> =
         listOf(
             ActiveMediaDeviceItemFactory(),
+            AudioSharingMediaDeviceItemFactory(localBluetoothManager),
             AvailableMediaDeviceItemFactory(),
             ConnectedDeviceItemFactory(),
             SavedDeviceItemFactory()
@@ -121,6 +122,7 @@
     private var displayPriority: List<DeviceItemType> =
         listOf(
             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+            DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
@@ -177,6 +179,9 @@
                         disconnect()
                         uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
                     }
+                    DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
+                    }
                     DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
                         setActive()
                         uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 02a40d9..dd71bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -16,16 +16,23 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
+import android.app.StatusBarManager.SESSION_KEYGUARD
 import com.android.compose.animation.scene.SceneKey
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
 import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
 import com.android.systemui.classifier.FalsingClassifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.log.SessionTracker
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -50,6 +57,8 @@
     private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
+    private val uiEventLogger: UiEventLogger,
+    private val sessionTracker: SessionTracker,
     sceneInteractor: SceneInteractor,
 ) {
     private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
@@ -162,6 +171,18 @@
         ) {
             _onIncorrectBouncerInput.emit(Unit)
         }
+
+        if (authenticationInteractor.getAuthenticationMethod() in setOf(Pin, Password, Pattern)) {
+            if (authResult == AuthenticationResult.SUCCEEDED) {
+                uiEventLogger.log(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS)
+            } else if (authResult == AuthenticationResult.FAILED) {
+                uiEventLogger.log(
+                    BouncerUiEvent.BOUNCER_PASSWORD_FAILURE,
+                    sessionTracker.getSessionId(SESSION_KEYGUARD)
+                )
+            }
+        }
+
         return authResult
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index e789475..62ef365 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import dagger.Module
 import dagger.Provides
 
@@ -42,11 +42,10 @@
     fun isOnlyComposeBouncerEnabled(): Boolean
 }
 
-class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFlags) :
-    ComposeBouncerFlags {
+class ComposeBouncerFlagsImpl() : ComposeBouncerFlags {
 
     override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
-        return sceneContainerFlags.isEnabled() || Flags.composeBouncer()
+        return SceneContainerFlag.isEnabled || Flags.composeBouncer()
     }
 
     @Deprecated(
@@ -55,7 +54,7 @@
         replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
     )
     override fun isOnlyComposeBouncerEnabled(): Boolean {
-        return !sceneContainerFlags.isEnabled() && Flags.composeBouncer()
+        return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
     }
 }
 
@@ -63,7 +62,7 @@
 object ComposeBouncerFlagsModule {
     @Provides
     @SysUISingleton
-    fun impl(sceneContainerFlags: SceneContainerFlags): ComposeBouncerFlags {
-        return ComposeBouncerFlagsImpl(sceneContainerFlags)
+    fun impl(): ComposeBouncerFlags {
+        return ComposeBouncerFlagsImpl()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt
new file mode 100644
index 0000000..3be5499
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/**
+ * Legacy bouncer UI events {@link com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent}.
+ * Only contains that used by metrics.
+ */
+enum class BouncerUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "Bouncer is dismissed using extended security access.")
+    BOUNCER_DISMISS_EXTENDED_ACCESS(413),
+
+    // PASSWORD here includes password, pattern, and pin.
+    @UiEvent(doc = "Bouncer is successfully unlocked using password.")
+    BOUNCER_PASSWORD_SUCCESS(418),
+    @UiEvent(doc = "An attempt to unlock bouncer using password has failed.")
+    BOUNCER_PASSWORD_FAILURE(419);
+
+    override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 12cac92..4c2380c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -135,8 +135,11 @@
 
         onIntentionalUserInput()
 
-        mutablePinInput.value = pinInput.append(input)
-        tryAuthenticate(useAutoConfirm = true)
+        val maxInputLength = hintedPinLength.value ?: Int.MAX_VALUE
+        if (pinInput.getPin().size < maxInputLength) {
+            mutablePinInput.value = pinInput.append(input)
+            tryAuthenticate(useAutoConfirm = true)
+        }
     }
 
     /** Notifies that the user clicked the backspace button. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index af467ef..613280c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -22,7 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.phone.NotificationTapHelper;
 
 import dagger.Binds;
@@ -51,9 +51,8 @@
     @SysUISingleton
     static FalsingCollector providesFalsingCollectorLegacy(
             FalsingCollectorImpl impl,
-            FalsingCollectorNoOp noOp,
-            SceneContainerFlags flags) {
-        return flags.isEnabled() ? noOp : impl;
+            FalsingCollectorNoOp noOp) {
+        return SceneContainerFlag.isEnabled() ? noOp : impl;
     }
 
     /** Provides the actual {@link FalsingCollector}. */
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
index d4a1f74..0c181e9 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
@@ -16,14 +16,14 @@
 
 package com.android.systemui.common.coroutine
 
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow as wrapped
 import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.ProducerScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.buffer
 import kotlinx.coroutines.flow.callbackFlow
 
+@Deprecated("Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead")
 object ConflatedCallbackFlow {
 
     /**
@@ -32,9 +32,15 @@
      * consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
      * we drop the oldest values automatically instead of suspending the producer.
      */
-    @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-    @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+    @Deprecated(
+        "Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead",
+        ReplaceWith(
+            "conflatedCallbackFlow",
+            "com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow"
+        )
+    )
+    @OptIn(ExperimentalTypeInference::class)
     fun <T> conflatedCallbackFlow(
         @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
-    ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+    ): Flow<T> = wrapped(block)
 }
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 373e1c9..619e052 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
@@ -55,7 +55,7 @@
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
@@ -107,7 +107,6 @@
     private val userManager: UserManager,
     private val dockManager: DockManager,
     sceneInteractor: SceneInteractor,
-    sceneContainerFlags: SceneContainerFlags,
     @CommunalLog logBuffer: LogBuffer,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) {
@@ -216,7 +215,7 @@
      */
     // TODO(b/323215860): rename to something more appropriate after cleaning up usages
     val isCommunalShowing: Flow<Boolean> =
-        flow { emit(sceneContainerFlags.isEnabled()) }
+        flow { emit(SceneContainerFlag.isEnabled) }
             .flatMapLatest { sceneContainerEnabled ->
                 if (sceneContainerEnabled) {
                     sceneInteractor.currentScene.map { it == Scenes.Communal }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7d86e06..6b85d30 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,6 +32,7 @@
 import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.accessibility.AccessibilityModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
+import com.android.systemui.ambient.dagger.AmbientModule;
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.authentication.AuthenticationModule;
@@ -162,14 +163,14 @@
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
+import kotlinx.coroutines.CoroutineScope;
+
 import java.util.Collections;
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
-import kotlinx.coroutines.CoroutineScope;
-
 /**
  * A dagger module for injecting components of System UI that are required by System UI.
  *
@@ -183,6 +184,7 @@
 @Module(includes = {
         AccessibilityModule.class,
         AccessibilityRepositoryModule.class,
+        AmbientModule.class,
         AppOpsModule.class,
         AssistModule.class,
         AuthenticationModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
deleted file mode 100644
index 989b0de..0000000
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.display.ui.view
-
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import android.view.WindowInsets
-import android.widget.TextView
-import androidx.core.view.updatePadding
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
-import com.android.systemui.statusbar.policy.ConfigurationController
-import kotlin.math.max
-
-/**
- * Dialog used to decide what to do with a connected display.
- *
- * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
- * pressed.
- */
-class MirroringConfirmationDialog(
-    context: Context,
-    private val onStartMirroringClickListener: View.OnClickListener,
-    private val onCancelMirroring: View.OnClickListener,
-    private val navbarBottomInsetsProvider: () -> Int,
-    configurationController: ConfigurationController? = null,
-    private val showConcurrentDisplayInfo: Boolean = false,
-    theme: Int = R.style.Theme_SystemUI_Dialog,
-) : SystemUIBottomSheetDialog(context, configurationController, theme) {
-
-    private lateinit var mirrorButton: TextView
-    private lateinit var dismissButton: TextView
-    private lateinit var dualDisplayWarning: TextView
-    private lateinit var bottomSheet: View
-    private var enabledPressed = false
-    private val defaultDialogBottomInset =
-        context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(R.layout.connected_display_dialog)
-
-        mirrorButton =
-            requireViewById<TextView>(R.id.enable_display).apply {
-                setOnClickListener(onStartMirroringClickListener)
-                enabledPressed = true
-            }
-        dismissButton =
-            requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
-
-        dualDisplayWarning =
-            requireViewById<TextView>(R.id.dual_display_warning).apply {
-                visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
-            }
-
-        bottomSheet = requireViewById(R.id.cd_bottom_sheet)
-
-        setOnDismissListener {
-            if (!enabledPressed) {
-                onCancelMirroring.onClick(null)
-            }
-        }
-        setupInsets()
-    }
-
-    private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
-        // This avoids overlap between dialog content and navigation bars.
-        // we only care about the bottom inset as in all other configuration where navigations
-        // are in other display sides there is no overlap with the dialog.
-        bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
-    }
-
-    override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
-        val navbarType = WindowInsets.Type.navigationBars()
-        if (changedTypes and navbarType != 0) {
-            setupInsets(insets.getInsets(navbarType).bottom)
-        }
-    }
-
-    override fun onConfigurationChanged() {
-        super.onConfigurationChanged()
-        setupInsets()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt
new file mode 100644
index 0000000..19b2673
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import android.widget.TextView
+import androidx.annotation.StyleRes
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.updatePadding
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.DialogDelegate
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
+import javax.inject.Inject
+import kotlin.math.max
+
+/**
+ * Dialog used to decide what to do with a connected display.
+ *
+ * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
+ * pressed.
+ */
+class MirroringConfirmationDialogDelegate
+@VisibleForTesting
+constructor(
+    context: Context,
+    private val showConcurrentDisplayInfo: Boolean = false,
+    private val onStartMirroringClickListener: View.OnClickListener,
+    private val onCancelMirroring: View.OnClickListener,
+    private val navbarBottomInsetsProvider: () -> Int,
+) : DialogDelegate<Dialog> {
+
+    private lateinit var mirrorButton: TextView
+    private lateinit var dismissButton: TextView
+    private lateinit var dualDisplayWarning: TextView
+    private lateinit var bottomSheet: View
+    private var enabledPressed = false
+    private val defaultDialogBottomInset =
+        context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
+
+    override fun onCreate(dialog: Dialog, savedInstanceState: Bundle?) {
+        dialog.setContentView(R.layout.connected_display_dialog)
+
+        mirrorButton =
+            dialog.requireViewById<TextView>(R.id.enable_display).apply {
+                setOnClickListener(onStartMirroringClickListener)
+                enabledPressed = true
+            }
+        dismissButton =
+            dialog.requireViewById<TextView>(R.id.cancel).apply {
+                setOnClickListener(onCancelMirroring)
+            }
+
+        dualDisplayWarning =
+            dialog.requireViewById<TextView>(R.id.dual_display_warning).apply {
+                visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
+            }
+
+        bottomSheet = dialog.requireViewById(R.id.cd_bottom_sheet)
+
+        dialog.setOnDismissListener {
+            if (!enabledPressed) {
+                onCancelMirroring.onClick(null)
+            }
+        }
+        setupInsets()
+    }
+
+    override fun onStart(dialog: Dialog) {
+        dialog.window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
+    }
+
+    override fun onStop(dialog: Dialog) {
+        dialog.window?.decorView?.setWindowInsetsAnimationCallback(null)
+    }
+
+    private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
+        // This avoids overlap between dialog content and navigation bars.
+        // we only care about the bottom inset as in all other configuration where navigations
+        // are in other display sides there is no overlap with the dialog.
+        bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+    }
+
+    override fun onConfigurationChanged(dialog: Dialog, configuration: Configuration) {
+        setupInsets()
+    }
+
+    private val insetsAnimationCallback =
+        object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+            private var lastInsets: WindowInsets? = null
+
+            override fun onEnd(animation: WindowInsetsAnimation) {
+                lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+            }
+
+            override fun onProgress(
+                insets: WindowInsets,
+                animations: MutableList<WindowInsetsAnimation>,
+            ): WindowInsets {
+                lastInsets = insets
+                onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+                return insets
+            }
+
+            private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+                animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+
+            private fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+                val navbarType = WindowInsets.Type.navigationBars()
+                if (changedTypes and navbarType != 0) {
+                    setupInsets(insets.getInsets(navbarType).bottom)
+                }
+            }
+        }
+
+    class Factory
+    @Inject
+    constructor(
+        @Application private val context: Context,
+        private val dialogFactory: SystemUIBottomSheetDialog.Factory,
+    ) {
+
+        fun createDialog(
+            showConcurrentDisplayInfo: Boolean = false,
+            onStartMirroringClickListener: View.OnClickListener,
+            onCancelMirroring: View.OnClickListener,
+            navbarBottomInsetsProvider: () -> Int,
+            @StyleRes theme: Int = R.style.Theme_SystemUI_Dialog,
+        ): Dialog =
+            dialogFactory.create(
+                delegate =
+                    MirroringConfirmationDialogDelegate(
+                        context = context,
+                        showConcurrentDisplayInfo = showConcurrentDisplayInfo,
+                        onStartMirroringClickListener = onStartMirroringClickListener,
+                        onCancelMirroring = onCancelMirroring,
+                        navbarBottomInsetsProvider = navbarBottomInsetsProvider,
+                    ),
+                theme = theme,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index fbf0538..81ea2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -25,8 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
-import com.android.systemui.display.ui.view.MirroringConfirmationDialog
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.display.ui.view.MirroringConfirmationDialogDelegate
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -54,7 +53,7 @@
     private val connectedDisplayInteractor: ConnectedDisplayInteractor,
     @Application private val scope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
-    private val configurationController: ConfigurationController,
+    private val bottomSheetFactory: MirroringConfirmationDialogDelegate.Factory,
 ) : CoreStartable {
 
     private var dialog: Dialog? = null
@@ -91,8 +90,8 @@
     private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) {
         hideDialog()
         dialog =
-            MirroringConfirmationDialog(
-                    context,
+            bottomSheetFactory
+                .createDialog(
                     onStartMirroringClickListener = {
                         scope.launch(bgDispatcher) { pendingDisplay.enable() }
                         hideDialog()
@@ -102,7 +101,6 @@
                         hideDialog()
                     },
                     navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom },
-                    configurationController,
                     showConcurrentDisplayInfo = concurrentDisplaysInProgess
                 )
                 .apply { show() }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index d0f2559..5a036b1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,14 +33,14 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
-import com.android.systemui.res.R;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.util.ViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 675e8de..1135afe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -43,11 +43,12 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.ambient.touch.TouchMonitor;
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
 import com.android.systemui.complication.Complication;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -94,6 +95,8 @@
 
     private final ComplicationComponent mComplicationComponent;
 
+    private final AmbientTouchComponent mAmbientTouchComponent;
+
     private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
             mDreamComplicationComponent;
 
@@ -102,7 +105,7 @@
     private final DreamOverlayLifecycleOwner mLifecycleOwner;
     private final LifecycleRegistry mLifecycleRegistry;
 
-    private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+    private TouchMonitor mTouchMonitor;
 
     private final KeyguardUpdateMonitorCallback mKeyguardCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -162,6 +165,7 @@
             com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
                     dreamComplicationComponentFactory,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+            AmbientTouchComponent.Factory ambientTouchComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             UiEventLogger uiEventLogger,
@@ -194,9 +198,11 @@
         mDreamComplicationComponent = dreamComplicationComponentFactory.create(
                 mComplicationComponent.getVisibilityController(), touchInsetManager);
         mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
-                mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
+                mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
+        mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
                 new HashSet<>(Arrays.asList(
-                        mDreamComplicationComponent.getHideComplicationTouchHandler())));
+                        mDreamComplicationComponent.getHideComplicationTouchHandler(),
+                        mDreamOverlayComponent.getCommunalTouchHandler())));
         mLifecycleOwner = lifecycleOwner;
         mLifecycleRegistry = mLifecycleOwner.getRegistry();
 
@@ -239,8 +245,8 @@
 
         mDreamOverlayContainerViewController =
                 mDreamOverlayComponent.getDreamOverlayContainerViewController();
-        mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
-        mDreamOverlayTouchMonitor.init();
+        mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+        mTouchMonitor.init();
 
         mStateController.setShouldShowComplications(shouldShowComplications());
 
@@ -370,7 +376,7 @@
         mStateController.setEntryAnimationsFinished(false);
 
         mDreamOverlayContainerViewController = null;
-        mDreamOverlayTouchMonitor = null;
+        mTouchMonitor = null;
 
         mWindow = null;
         mStarted = false;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index d525ce3..f8ae5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -26,11 +26,11 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.ambient.touch.TouchHandler;
+import com.android.systemui.ambient.touch.TouchMonitor;
 import com.android.systemui.complication.Complication;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -47,12 +47,12 @@
  * {@link HideComplicationTouchHandler} is responsible for hiding the overlay complications from
  * visibility whenever there is touch interactions outside the overlay. The overlay interaction
  * scope includes touches to the complication plus any touch entry region for gestures as specified
- * to the {@link DreamOverlayTouchMonitor}.
+ * to the {@link TouchMonitor}.
  *
- * This {@link DreamTouchHandler} is also responsible for fading in the complications at the end
- * of the {@link com.android.systemui.dreams.touch.DreamTouchHandler.TouchSession}.
+ * This {@link TouchHandler} is also responsible for fading in the complications at the end
+ * of the {@link TouchHandler.TouchSession}.
  */
-public class HideComplicationTouchHandler implements DreamTouchHandler {
+public class HideComplicationTouchHandler implements TouchHandler {
     private static final String TAG = "HideComplicationHandler";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index ba74742..31710ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -25,6 +25,7 @@
 
 import com.android.dream.lowlight.dagger.LowLightDreamModule;
 import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.ambient.touch.scrim.dagger.ScrimModule;
 import com.android.systemui.complication.dagger.RegisteredComplicationsModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -34,7 +35,6 @@
 import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
 import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
-import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
 import com.android.systemui.res.R;
 import com.android.systemui.touch.TouchInsetManager;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index cb587c2..16276fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -16,19 +16,14 @@
 
 package com.android.systemui.dreams.dagger;
 
-import static com.android.systemui.dreams.dagger.DreamOverlayModule.DREAM_TOUCH_HANDLERS;
-
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import android.annotation.Nullable;
-
 import androidx.lifecycle.LifecycleOwner;
 
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.DreamOverlayContainerViewController;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
+import com.android.systemui.dreams.touch.CommunalTouchHandler;
+import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
 import com.android.systemui.touch.TouchInsetManager;
 
 import dagger.BindsInstance;
@@ -36,17 +31,15 @@
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
-import java.util.Set;
 
-import javax.inject.Named;
 import javax.inject.Scope;
 
 /**
  * Dagger subcomponent for {@link DreamOverlayModule}.
  */
 @Subcomponent(modules = {
-        DreamTouchModule.class,
         DreamOverlayModule.class,
+        CommunalTouchModule.class
 })
 @DreamOverlayComponent.DreamOverlayScope
 public interface DreamOverlayComponent {
@@ -56,9 +49,7 @@
         DreamOverlayComponent create(
                 @BindsInstance LifecycleOwner lifecycleOwner,
                 @BindsInstance ComplicationHostViewController complicationHostViewController,
-                @BindsInstance TouchInsetManager touchInsetManager,
-                @BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
-                        Set<DreamTouchHandler> dreamTouchHandlers);
+                @BindsInstance TouchInsetManager touchInsetManager);
     }
 
     /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -70,6 +61,6 @@
     /** Builds a {@link DreamOverlayContainerViewController}. */
     DreamOverlayContainerViewController getDreamOverlayContainerViewController();
 
-    /** Builds a {@link DreamOverlayTouchMonitor} */
-    DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
+    /** Builds communal touch handler */
+    CommunalTouchHandler getCommunalTouchHandler();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 34dd1008..999e681 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.dreams.dagger;
 
-import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -25,26 +24,20 @@
 import androidx.lifecycle.LifecycleOwner;
 
 import com.android.internal.util.Preconditions;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.res.R;
 import com.android.systemui.touch.TouchInsetManager;
 
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
 
 import javax.inject.Named;
 
 /** Dagger module for {@link DreamOverlayComponent}. */
 @Module
 public abstract class DreamOverlayModule {
-    public static final String DREAM_TOUCH_HANDLERS = "dream_touch_handlers";
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -169,11 +162,4 @@
     static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
         return lifecycleOwner.getLifecycle();
     }
-
-    @Provides
-    @ElementsIntoSet
-    static Set<DreamTouchHandler> providesDreamTouchHandlers(
-            @Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
-        return touchHandlers != null ? touchHandlers : new HashSet<>();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 13588c2..1c047dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.dreams.touch;
 
-import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.graphics.Rect;
@@ -27,7 +26,9 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.Lifecycle;
 
+import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import java.util.Optional;
@@ -36,8 +37,8 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 
-/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
-public class CommunalTouchHandler implements DreamTouchHandler {
+/** {@link TouchHandler} responsible for handling touches to open communal hub. **/
+public class CommunalTouchHandler implements TouchHandler {
     private final int mInitiationWidth;
     private final Optional<CentralSurfaces> mCentralSurfaces;
     private final Lifecycle mLifecycle;
@@ -53,7 +54,7 @@
     @Inject
     public CommunalTouchHandler(
             Optional<CentralSurfaces> centralSurfaces,
-            @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
+            @Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
             CommunalInteractor communalInteractor,
             Lifecycle lifecycle) {
         mInitiationWidth = initiationWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt
new file mode 100644
index 0000000..927ea4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.touch.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+interface CommunalTouchModule {
+    companion object {
+        /** Provides the width of the gesture area for swiping open communal hub. */
+        @JvmStatic
+        @Provides
+        @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
+        fun providesCommunalGestureInitiationWidth(@Main resources: Resources): Int {
+            return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width)
+        }
+
+        /** Width of swipe gesture edge to show communal hub. */
+        const val COMMUNAL_GESTURE_INITIATION_WIDTH = "communal_gesture_initiation_width"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
deleted file mode 100644
index b719126..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.touch.dagger;
-
-import dagger.Module;
-
-/**
- * {@link DreamTouchModule} encapsulates dream touch-related components.
- */
-@Module(includes = {
-            BouncerSwipeModule.class,
-            ShadeModule.class,
-        }, subcomponents = {
-            InputSessionComponent.class,
-})
-public interface DreamTouchModule {
-    String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
-    String PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2a9dad0..21af0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1373,7 +1373,7 @@
     private final Lazy<DreamViewModel> mDreamViewModel;
     private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
-    private Boolean mShowCommunalByDefault;
+    private boolean mShowCommunalByDefault = false;
 
     private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
 
@@ -3559,15 +3559,14 @@
             ShadeLockscreenInteractor shadeLockscreenInteractor,
             @Nullable ShadeExpansionStateManager shadeExpansionStateManager,
             BiometricUnlockController biometricUnlockController,
-            View notificationContainer, KeyguardBypassController bypassController) {
+            View notificationContainer) {
         mCentralSurfaces = centralSurfaces;
         mKeyguardViewControllerLazy.get().registerCentralSurfaces(
                 centralSurfaces,
                 shadeLockscreenInteractor,
                 shadeExpansionStateManager,
                 biometricUnlockController,
-                notificationContainer,
-                bypassController);
+                notificationContainer);
         return mKeyguardViewControllerLazy.get();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index bf1f074..eef4b97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -52,7 +52,6 @@
     @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-    private val sceneContainerFlags: SceneContainerFlags,
     private val sceneInteractor: SceneInteractor,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -75,7 +74,7 @@
         get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
 
     private val isBouncerSceneActive: Flow<Boolean> =
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             sceneInteractor.currentScene.map { it == Scenes.Bouncer }.distinctUntilChanged()
         } else {
             flowOf(false)
@@ -115,7 +114,7 @@
             .distinctUntilChanged()
 
     private fun isBouncerActive(): Boolean {
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             return sceneInteractor.currentScene.value == Scenes.Bouncer
         }
         return primaryBouncerInteractor.isBouncerShowing() &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c476948..7224536 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -44,7 +44,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.CommandQueue
@@ -84,7 +84,6 @@
     private val repository: KeyguardRepository,
     private val commandQueue: CommandQueue,
     powerInteractor: PowerInteractor,
-    sceneContainerFlags: SceneContainerFlags,
     bouncerRepository: KeyguardBouncerRepository,
     configurationInteractor: ConfigurationInteractor,
     shadeRepository: ShadeRepository,
@@ -331,7 +330,7 @@
 
     /** Whether to animate the next doze mode transition. */
     val animateDozingTransitions: Flow<Boolean> by lazy {
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             sceneInteractorProvider
                 .get()
                 .transitioningTo
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 80e94a2..20b7b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -23,12 +23,14 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.kotlin.toPx
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 
 /**
  * Distance over which the surface behind the keyguard is animated in during a Y-translation
@@ -96,13 +98,21 @@
             .distinctUntilChanged()
 
     /**
+     * Whether a notification launch animation is running when we're not already in the GONE state.
+     */
+    private val isNotificationLaunchAnimationRunningOnKeyguard =
+        notificationLaunchInteractor.isLaunchAnimationRunning
+            .sample(transitionInteractor.finishedKeyguardState)
+            .map { it != KeyguardState.GONE }
+
+    /**
      * Whether we're animating the surface, or a notification launch animation is running (which
      * means we're going to animate the surface, even if animators aren't yet running).
      */
     val isAnimatingSurface =
         combine(
             repository.isAnimatingSurface,
-            notificationLaunchInteractor.isLaunchAnimationRunning
+            isNotificationLaunchAnimationRunningOnKeyguard,
         ) { animatingSurface, animatingLaunch ->
             animatingSurface || animatingLaunch
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index b5d6177..6b8e896 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
 import javax.inject.Inject
 import kotlin.math.max
 
@@ -217,15 +218,21 @@
         if (!DEBUG || currentClock == null) return
         val smallClockViewId = R.id.lockscreen_clock_view
         val largeClockViewId = currentClock.largeClock.layout.views[0].id
+        val smartspaceDateId = sharedR.id.date_smartspace_view
         Log.i(
             TAG,
             "applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
-                "alpha=${cs.getConstraint(smallClockViewId).propertySet}"
+                "alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha}"
         )
         Log.i(
             TAG,
             "applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
-                "alpha=${cs.getConstraint(largeClockViewId).propertySet}"
+                "alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha}"
+        )
+        Log.i(
+            TAG,
+            "applyCsToSmartspaceDate: vis=${cs.getVisibility(smartspaceDateId)} " +
+                "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}"
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 6255f0d..7178e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
 import kotlinx.coroutines.launch
 
 object KeyguardClockViewBinder {
@@ -76,13 +75,13 @@
                 }
                 launch {
                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
+                    viewModel.clockShouldBeCentered.collect {
                         viewModel.currentClock.value?.let {
-                            // Weather clock also has hasCustomPositionUpdatedAnimation as true
-                            // TODO(b/323020908): remove ID check
+                            // TODO(b/301502635): remove "!it.config.useCustomClockScene" when
+                            // migrate clocks to blueprint is fully rolled out
                             if (
                                 it.largeClock.config.hasCustomPositionUpdatedAnimation &&
-                                    it.config.id == DEFAULT_CLOCK_ID
+                                    !it.config.useCustomClockScene
                             ) {
                                 blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
                             } else {
@@ -93,12 +92,9 @@
                 }
                 launch {
                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
+                    viewModel.isAodIconsVisible.collect {
                         viewModel.currentClock.value?.let {
-                            // Weather clock also has hasCustomPositionUpdatedAnimation as true
-                            if (
-                                viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
-                            ) {
+                            if (viewModel.useLargeClock && it.config.useCustomClockScene) {
                                 blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 5ee35e4f..cc54920 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -30,6 +30,9 @@
 import android.view.ViewGroup.OnHierarchyChangeListener
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
@@ -49,6 +52,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -125,6 +129,21 @@
         disposables +=
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    if (ComposeLockscreen.isEnabled) {
+                        view.setViewTreeOnBackPressedDispatcherOwner(
+                            object : OnBackPressedDispatcherOwner {
+                                override val onBackPressedDispatcher =
+                                    OnBackPressedDispatcher().apply {
+                                        setOnBackInvokedDispatcher(
+                                            view.viewRootImpl.onBackInvokedDispatcher
+                                        )
+                                    }
+
+                                override val lifecycle: Lifecycle =
+                                    this@repeatWhenAttached.lifecycle
+                            }
+                        )
+                    }
                     launch {
                         occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
                             ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eaa5e33..9ec7a65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.view.View
-import android.view.View.GONE
 import android.view.ViewTreeObserver.OnGlobalLayoutListener
 import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
@@ -205,8 +204,7 @@
         smartspaceController.requestSmartspaceUpdate()
 
         constraintSet.apply {
-            setVisibility(
-                sharedR.id.weather_smartspace_view,
+            val weatherVisibility =
                 when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
                     true -> ConstraintSet.GONE
                     false ->
@@ -215,11 +213,18 @@
                             false -> ConstraintSet.GONE
                         }
                 }
+            setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility)
+            setAlpha(
+                sharedR.id.weather_smartspace_view,
+                if (weatherVisibility == ConstraintSet.VISIBLE) 1f else 0f
             )
-            setVisibility(
-                sharedR.id.date_smartspace_view,
+            val dateVisibility =
                 if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
                 else ConstraintSet.VISIBLE
+            setVisibility(sharedR.id.date_smartspace_view, dateVisibility)
+            setAlpha(
+                sharedR.id.date_smartspace_view,
+                if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 49fffdd..45dca99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
@@ -64,7 +64,6 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val keyguardInteractor: KeyguardInteractor,
     val viewModel: AodToLockscreenTransitionViewModel,
-    private val sceneContainerFlags: SceneContainerFlags,
     private val keyguardViewController: Lazy<KeyguardViewController>,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
@@ -242,7 +241,7 @@
         }
 
     suspend fun onLongPress() {
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             deviceEntryInteractor.attemptDeviceEntry()
         } else {
             keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f6da033..a6d3312 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -118,8 +118,7 @@
                 currentClock
             ) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock ->
                 val shouldUseSplitShade = shadeMode == ShadeMode.Split
-                // TODO(b/326098079): make id a constant field in config
-                if (currentClock?.config?.id == "DIGITAL_CLOCK_WEATHER") {
+                if (currentClock?.config?.useCustomClockScene == true) {
                     val weatherClockLayout =
                         when {
                             shouldUseSplitShade && clockShouldBeCentered ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
new file mode 100644
index 0000000..cfe5cde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.data.model
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.shared.model.MediaData.Companion.PLAYBACK_LOCAL
+
+data class MediaSortKeyModel(
+    /** Whether the item represents a Smartspace media recommendation that should be prioritized. */
+    val isPrioritizedRec: Boolean = false,
+    val isPlaying: Boolean? = null,
+    val playbackLocation: Int = PLAYBACK_LOCAL,
+    val active: Boolean = true,
+    val isResume: Boolean = false,
+    val lastActive: Long = 0L,
+    val notificationKey: String? = null,
+    val updateTime: Long = 0,
+    val instanceId: InstanceId? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 9dc5900..7e57cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -18,10 +18,14 @@
 
 import com.android.internal.logging.InstanceId
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.data.model.MediaSortKeyModel
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.util.time.SystemClock
+import java.util.TreeMap
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -29,7 +33,7 @@
 
 /** A repository that holds the state of filtered media data on the device. */
 @SysUISingleton
-class MediaFilterRepository @Inject constructor() {
+class MediaFilterRepository @Inject constructor(private val systemClock: SystemClock) {
 
     /** Instance id of media control that recommendations card reactivated. */
     private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
@@ -58,6 +62,26 @@
     val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> =
         _recommendationsLoadingState.asStateFlow()
 
+    private val comparator =
+        compareByDescending<MediaSortKeyModel> {
+                it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_LOCAL
+            }
+            .thenByDescending {
+                it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+            }
+            .thenByDescending { it.active }
+            .thenByDescending { it.isPrioritizedRec }
+            .thenByDescending { !it.isResume }
+            .thenByDescending { it.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+            .thenByDescending { it.lastActive }
+            .thenByDescending { it.updateTime }
+            .thenByDescending { it.notificationKey }
+
+    private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> =
+        MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator))
+    val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> =
+        _sortedMedia.asStateFlow()
+
     fun addMediaEntry(key: String, data: MediaData) {
         val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
         entries[key] = data
@@ -138,9 +162,91 @@
                 } else {
                     emptyList()
                 }
+
+        addMediaLoadingToSortedMap(mediaDataLoadingModel)
     }
 
-    fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
+    fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
         _recommendationsLoadingState.value = smartspaceMediaLoadingModel
+
+        addRecsLoadingToSortedMap(smartspaceMediaLoadingModel)
+    }
+
+    private fun addMediaLoadingToSortedMap(mediaDataLoadingModel: MediaDataLoadingModel) {
+        val instanceId =
+            when (mediaDataLoadingModel) {
+                is MediaDataLoadingModel.Loaded -> mediaDataLoadingModel.instanceId
+                is MediaDataLoadingModel.Removed -> mediaDataLoadingModel.instanceId
+                MediaDataLoadingModel.Unknown -> null
+            }
+        val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+        sortedMap.putAll(
+            _sortedMedia.value.filter { (_, commonModel) ->
+                commonModel !is MediaCommonModel.MediaControl ||
+                    commonModel.instanceId != instanceId
+            }
+        )
+
+        _selectedUserEntries.value[instanceId]?.let {
+            val sortKey =
+                MediaSortKeyModel(
+                    isPrioritizedRec = false,
+                    it.isPlaying,
+                    it.playbackLocation,
+                    it.active,
+                    it.resumption,
+                    it.lastActive,
+                    it.notificationKey,
+                    systemClock.currentTimeMillis(),
+                    it.instanceId,
+                )
+
+            if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+                sortedMap[sortKey] = MediaCommonModel.MediaControl(it.instanceId)
+            }
+        }
+
+        _sortedMedia.value = sortedMap
+    }
+
+    private fun addRecsLoadingToSortedMap(
+        smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel
+    ) {
+        val isPrioritized: Boolean
+        val key: String?
+        when (smartspaceMediaLoadingModel) {
+            is SmartspaceMediaLoadingModel.Loaded -> {
+                isPrioritized = smartspaceMediaLoadingModel.isPrioritized
+                key = smartspaceMediaLoadingModel.key
+            }
+            is SmartspaceMediaLoadingModel.Removed -> {
+                isPrioritized = false
+                key = smartspaceMediaLoadingModel.key
+            }
+            SmartspaceMediaLoadingModel.Unknown -> {
+                isPrioritized = false
+                key = null
+            }
+        }
+        val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+        sortedMap.putAll(
+            _sortedMedia.value.filter { (_, commonModel) ->
+                commonModel !is MediaCommonModel.MediaRecommendations || commonModel.key != key
+            }
+        )
+
+        key?.let {
+            val sortKey =
+                MediaSortKeyModel(
+                    isPrioritizedRec = isPrioritized,
+                    isPlaying = false,
+                    active = _smartspaceMediaData.value.isActive,
+                )
+            if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) {
+                sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(key)
+            }
+        }
+
+        _sortedMedia.value = sortedMap
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index a30e582..5432a18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -69,7 +69,11 @@
     private val mediaFlags: MediaFlags,
     private val mediaFilterRepository: MediaFilterRepository,
 ) : MediaDataManager.Listener {
-    lateinit var mediaDataManager: MediaDataManager
+    /** Non-UI listeners to media changes. */
+    private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf()
+    val listeners: Set<MediaDataProcessor.Listener>
+        get() = _listeners.toSet()
+    lateinit var mediaDataProcessor: MediaDataProcessor
 
     // Ensure the field (and associated reference) isn't removed during optimization.
     @KeepForWeakReference
@@ -113,6 +117,9 @@
         mediaFilterRepository.addMediaDataLoadingState(
             MediaDataLoadingModel.Loaded(data.instanceId)
         )
+
+        // Notify listeners
+        listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
     }
 
     override fun onSmartspaceMediaDataLoaded(
@@ -171,6 +178,20 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Loaded(lastActiveId)
                 )
+                listeners.forEach { listener ->
+                    getKey(lastActiveId)?.let { lastActiveKey ->
+                        listener.onMediaDataLoaded(
+                            lastActiveKey,
+                            lastActiveKey,
+                            mediaData,
+                            receivedSmartspaceCardLatency =
+                                (systemClock.currentTimeMillis() -
+                                        data.headphoneConnectionTimeMillis)
+                                    .toInt(),
+                            isSsReactivated = true
+                        )
+                    }
+                }
             }
         } else if (data.isActive) {
             // Mark to prioritize Smartspace card if no recent media.
@@ -186,9 +207,10 @@
             smartspaceMediaData.packageName,
             smartspaceMediaData.instanceId
         )
-        mediaFilterRepository.setRecommedationsLoadingState(
+        mediaFilterRepository.setRecommendationsLoadingState(
             SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
         )
+        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -198,6 +220,8 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Removed(instanceId)
                 )
+                // Only notify listeners if something actually changed
+                listeners.forEach { it.onMediaDataRemoved(key) }
             }
         }
     }
@@ -212,6 +236,11 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Loaded(lastActiveId, immediately)
                 )
+                listeners.forEach { listener ->
+                    getKey(lastActiveId)?.let { lastActiveKey ->
+                        listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately)
+                    }
+                }
             }
         }
 
@@ -224,9 +253,10 @@
                 )
             )
         }
-        mediaFilterRepository.setRecommedationsLoadingState(
+        mediaFilterRepository.setRecommendationsLoadingState(
             SmartspaceMediaLoadingModel.Removed(key, immediately)
         )
+        listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
     }
 
     @VisibleForTesting
@@ -240,6 +270,7 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Removed(data.instanceId)
                 )
+                listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
             }
         }
     }
@@ -247,6 +278,7 @@
     @VisibleForTesting
     internal fun handleUserSwitched() {
         // If the user changes, remove all current MediaData objects.
+        val listenersCopy = listeners
         val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList()
         // Clear the list first and update loading state to remove media from UI.
         mediaFilterRepository.clearSelectedUserMedia()
@@ -255,6 +287,9 @@
             mediaFilterRepository.addMediaDataLoadingState(
                 MediaDataLoadingModel.Removed(instanceId)
             )
+            getKey(instanceId)?.let {
+                listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+            }
         }
 
         mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
@@ -268,6 +303,7 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Loaded(data.instanceId)
                 )
+                listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
             }
         }
     }
@@ -279,7 +315,7 @@
         mediaEntries.forEach { (key, data) ->
             if (mediaFilterRepository.selectedUserEntries.value.containsKey(data.instanceId)) {
                 // Force updates to listeners, needed for re-activated card
-                mediaDataManager.setInactive(key, timedOut = true, forceUpdate = true)
+                mediaDataProcessor.setInactive(key, timedOut = true, forceUpdate = true)
             }
         }
         val smartspaceMediaData = mediaFilterRepository.smartspaceMediaData.value
@@ -301,7 +337,7 @@
 
             if (mediaFlags.isPersistentSsCardEnabled()) {
                 mediaFilterRepository.setRecommendation(smartspaceMediaData.copy(isActive = false))
-                mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId)
+                mediaDataProcessor.setRecommendationInactive(smartspaceMediaData.targetId)
             } else {
                 mediaFilterRepository.setRecommendation(
                     EMPTY_SMARTSPACE_MEDIA_DATA.copy(
@@ -309,7 +345,7 @@
                         instanceId = smartspaceMediaData.instanceId,
                     )
                 )
-                mediaDataManager.dismissSmartspaceRecommendation(
+                mediaDataProcessor.dismissSmartspaceRecommendation(
                     smartspaceMediaData.targetId,
                     delay = 0L,
                 )
@@ -317,6 +353,12 @@
         }
     }
 
+    /** Add a listener for filtered [MediaData] changes */
+    fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener)
+
+    /** Remove a listener that was registered with addListener */
+    fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener)
+
     /**
      * Return the time since last active for the most-recent media.
      *
@@ -336,6 +378,16 @@
         return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
     }
 
+    private fun getKey(instanceId: InstanceId): String? {
+        val allEntries = mediaFilterRepository.allUserEntries.value
+        val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId }
+        return if (filteredEntries.isNotEmpty()) {
+            filteredEntries.keys.first()
+        } else {
+            null
+        }
+    }
+
     companion object {
         /**
          * Maximum age of a media control to re-activate on smartspace signal. If there is no media
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index cdcf363..b04e938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -34,8 +34,10 @@
 import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter
 import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.media.controls.util.MediaControlsRefactorFlag
 import com.android.systemui.media.controls.util.MediaFlags
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -46,6 +48,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
@@ -120,6 +123,10 @@
     val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> =
         mediaFilterRepository.recommendationsLoadingState
 
+    /** The most recent sorted set for user media instances */
+    val sortedMedia: Flow<List<MediaCommonModel>> =
+        mediaFilterRepository.sortedMedia.map { it.values.toList() }
+
     override fun start() {
         if (!mediaFlags.isMediaControlsRefactorEnabled()) {
             return
@@ -150,13 +157,19 @@
             mediaDataProcessor.onSessionDestroyed(key)
         }
         mediaResumeListener.setManager(this)
-        mediaDataFilter.mediaDataManager = this
+        mediaDataFilter.mediaDataProcessor = mediaDataProcessor
     }
 
-    override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) {
-        mediaDataProcessor.setInactive(key, timedOut, forceUpdate)
+    override fun addListener(listener: MediaDataManager.Listener) {
+        mediaDataFilter.addListener(listener)
     }
 
+    override fun removeListener(listener: MediaDataManager.Listener) {
+        mediaDataFilter.removeListener(listener)
+    }
+
+    override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported
+
     override fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
         mediaDataProcessor.onNotificationAdded(key, sbn)
     }
@@ -201,9 +214,7 @@
         return mediaDataProcessor.dismissSmartspaceRecommendation(key, delay)
     }
 
-    override fun setRecommendationInactive(key: String) {
-        mediaDataProcessor.setRecommendationInactive(key)
-    }
+    override fun setRecommendationInactive(key: String) = unsupported
 
     override fun onNotificationRemoved(key: String) {
         mediaDataProcessor.onNotificationRemoved(key)
@@ -234,4 +245,12 @@
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         mediaDeviceManager.dump(pw)
     }
+
+    companion object {
+        val unsupported: Nothing
+            get() =
+                error(
+                    "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled"
+                )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
new file mode 100644
index 0000000..83e2765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import com.android.internal.logging.InstanceId
+
+/** Models any type of media. */
+sealed class MediaCommonModel {
+    data class MediaControl(val instanceId: InstanceId) : MediaCommonModel()
+
+    data class MediaRecommendations(val key: String) : MediaCommonModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
new file mode 100644
index 0000000..14a9179
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.binder
+
+import android.widget.ImageButton
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.res.R
+
+object MediaControlViewBinder {
+
+    fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
+        setVisibleAndAlpha(set, resId, visible, ConstraintSet.GONE)
+    }
+
+    private fun setVisibleAndAlpha(
+        set: ConstraintSet,
+        resId: Int,
+        visible: Boolean,
+        notVisibleValue: Int
+    ) {
+        set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue)
+        set.setAlpha(resId, if (visible) 1.0f else 0.0f)
+    }
+
+    fun updateSeekBarVisibility(constraintSet: ConstraintSet, isSeekBarEnabled: Boolean) {
+        if (isSeekBarEnabled) {
+            constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.VISIBLE)
+            constraintSet.setAlpha(R.id.media_progress_bar, 1.0f)
+        } else {
+            constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.INVISIBLE)
+            constraintSet.setAlpha(R.id.media_progress_bar, 0.0f)
+        }
+    }
+
+    fun setSemanticButtonVisibleAndAlpha(
+        button: ImageButton,
+        expandedSet: ConstraintSet,
+        collapsedSet: ConstraintSet,
+        visible: Boolean,
+        notVisibleValue: Int,
+        showInCollapsed: Boolean
+    ) {
+        if (notVisibleValue == ConstraintSet.INVISIBLE) {
+            // Since time views should appear instead of buttons.
+            button.isFocusable = visible
+            button.isClickable = visible
+        }
+        setVisibleAndAlpha(expandedSet, button.id, visible, notVisibleValue)
+        setVisibleAndAlpha(collapsedSet, button.id, visible = visible && showInCollapsed)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
new file mode 100644
index 0000000..9c6d59e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.binder
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.graphics.Matrix
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
+import com.android.systemui.media.controls.ui.controller.MediaViewController
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.util.animation.TransitionLayout
+import kotlin.math.min
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+object MediaRecommendationsViewBinder {
+
+    /** Binds recommendations view holder to the given view-model */
+    fun bind(
+        viewHolder: RecommendationViewHolder,
+        viewModel: MediaRecommendationsViewModel,
+        mediaViewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility
+        val cardView = viewHolder.recommendations
+        cardView.repeatWhenAttached {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.mediaRecsCard.collectLatest { viewModel ->
+                            viewModel?.let {
+                                bindRecsCard(viewHolder, it, mediaViewController, falsingManager)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private fun bindRecsCard(
+        viewHolder: RecommendationViewHolder,
+        viewModel: MediaRecsCardViewModel,
+        mediaViewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        // Bind main card.
+        viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor)
+        viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor)
+        viewHolder.recommendations.contentDescription =
+            viewModel.contentDescription.invoke(mediaViewController.isGutsVisible)
+
+        viewHolder.recommendations.setOnClickListener {
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+            viewModel.onClicked(Expandable.fromView(it))
+        }
+
+        viewHolder.recommendations.setOnLongClickListener {
+            if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
+                return@setOnLongClickListener true
+            if (!mediaViewController.isGutsVisible) {
+                openGuts(viewHolder, viewModel, mediaViewController)
+            } else {
+                closeGuts(viewHolder, viewModel, mediaViewController)
+            }
+            return@setOnLongClickListener true
+        }
+
+        // Bind all recommendations.
+        bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager)
+        updateRecommendationsVisibility(mediaViewController, viewHolder.recommendations)
+
+        // Set visibility of recommendations.
+        val expandedSet: ConstraintSet = mediaViewController.expandedLayout
+        val collapsedSet: ConstraintSet = mediaViewController.collapsedLayout
+        viewHolder.mediaTitles.forEach {
+            setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible)
+            setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible)
+        }
+        viewHolder.mediaSubtitles.forEach {
+            setVisibleAndAlpha(expandedSet, it.id, viewModel.areSubtitlesVisible)
+            setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible)
+        }
+
+        bindRecommendationsGuts(viewHolder, viewModel, mediaViewController, falsingManager)
+
+        mediaViewController.refreshState()
+    }
+
+    private fun bindRecommendationsGuts(
+        viewHolder: RecommendationViewHolder,
+        viewModel: MediaRecsCardViewModel,
+        mediaViewController: MediaViewController,
+        falsingManager: FalsingManager,
+    ) {
+        val gutsViewHolder = viewHolder.gutsViewHolder
+        val gutsViewModel = viewModel.gutsMenu
+
+        gutsViewHolder.gutsText.text = gutsViewModel.gutsText
+        gutsViewHolder.dismissText.visibility = View.VISIBLE
+        gutsViewHolder.dismiss.isEnabled = true
+        gutsViewHolder.dismiss.setOnClickListener {
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+            closeGuts(viewHolder, viewModel, mediaViewController)
+            gutsViewModel.onDismissClicked.invoke()
+        }
+
+        gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground
+        gutsViewHolder.cancel.setOnClickListener {
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                closeGuts(viewHolder, viewModel, mediaViewController)
+            }
+        }
+
+        gutsViewHolder.settings.setOnClickListener {
+            if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                gutsViewModel.onSettingsClicked.invoke()
+            }
+        }
+
+        gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled)
+        gutsViewHolder.setTextPrimaryColor(gutsViewModel.textPrimaryColor)
+        gutsViewHolder.setAccentPrimaryColor(gutsViewModel.accentPrimaryColor)
+        gutsViewHolder.setSurfaceColor(gutsViewModel.surfaceColor)
+    }
+
+    private fun bindRecommendationsList(
+        viewHolder: RecommendationViewHolder,
+        mediaRecs: List<MediaRecViewModel>,
+        falsingManager: FalsingManager
+    ) {
+        mediaRecs.forEachIndexed { index, mediaRecViewModel ->
+            if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed
+
+            val appIconView = viewHolder.mediaAppIcons[index]
+            appIconView.clearColorFilter()
+            if (mediaRecViewModel.appIcon != null) {
+                appIconView.setImageDrawable(mediaRecViewModel.appIcon)
+            } else {
+                appIconView.setImageResource(R.drawable.ic_music_note)
+            }
+
+            val mediaCoverContainer = viewHolder.mediaCoverContainers[index]
+            mediaCoverContainer.setOnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+                mediaRecViewModel.onClicked.invoke(Expandable.fromView(it), index)
+            }
+            mediaCoverContainer.setOnLongClickListener {
+                if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
+                    return@setOnLongClickListener true
+                (it.parent as View).performLongClick()
+                return@setOnLongClickListener true
+            }
+
+            val mediaCover = viewHolder.mediaCoverItems[index]
+            val width: Int =
+                mediaCover.context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
+            val height: Int =
+                mediaCover.context.resources.getDimensionPixelSize(
+                    R.dimen.qs_media_rec_album_height_expanded
+                )
+            val coverMatrix = Matrix(mediaCover.imageMatrix)
+            coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height)
+            mediaCover.imageMatrix = coverMatrix
+            mediaCover.setImageDrawable(mediaRecViewModel.albumIcon)
+            mediaCover.contentDescription = mediaRecViewModel.contentDescription
+
+            val title = viewHolder.mediaTitles[index]
+            title.text = mediaRecViewModel.title
+            title.setTextColor(ColorStateList.valueOf(mediaRecViewModel.titleColor))
+
+            val subtitle = viewHolder.mediaSubtitles[index]
+            subtitle.text = mediaRecViewModel.subtitle
+            subtitle.setTextColor(ColorStateList.valueOf(mediaRecViewModel.subtitleColor))
+
+            val progressBar = viewHolder.mediaProgressBars[index]
+            progressBar.progress = mediaRecViewModel.progress
+            progressBar.progressTintList = ColorStateList.valueOf(mediaRecViewModel.progressColor)
+            if (mediaRecViewModel.progress == 0) {
+                progressBar.visibility = View.GONE
+            }
+        }
+    }
+
+    private fun openGuts(
+        viewHolder: RecommendationViewHolder,
+        viewModel: MediaRecsCardViewModel,
+        mediaViewController: MediaViewController,
+    ) {
+        viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
+        mediaViewController.openGuts()
+        viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(true)
+        viewModel.onLongClicked.invoke()
+    }
+
+    private fun closeGuts(
+        viewHolder: RecommendationViewHolder,
+        mediaRecsCardViewModel: MediaRecsCardViewModel,
+        mediaViewController: MediaViewController,
+    ) {
+        viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
+        mediaViewController.closeGuts(false)
+        viewHolder.recommendations.contentDescription =
+            mediaRecsCardViewModel.contentDescription.invoke(false)
+    }
+
+    private fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
+        set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else ConstraintSet.GONE)
+        set.setAlpha(resId, if (visible) 1.0f else 0.0f)
+    }
+
+    private fun updateRecommendationsVisibility(
+        mediaViewController: MediaViewController,
+        cardView: TransitionLayout,
+    ) {
+        val fittedRecsNum = getNumberOfFittedRecommendations(cardView.context)
+        val expandedSet = mediaViewController.expandedLayout
+        val collapsedSet = mediaViewController.collapsedLayout
+        val mediaCoverContainers = getMediaCoverContainers(cardView)
+        // Hide media cover that cannot fit in the recommendation card.
+        mediaCoverContainers.forEachIndexed { index, container ->
+            setVisibleAndAlpha(expandedSet, container.id, index < fittedRecsNum)
+            setVisibleAndAlpha(collapsedSet, container.id, index < fittedRecsNum)
+        }
+    }
+
+    private fun getMediaCoverContainers(cardView: TransitionLayout): List<ViewGroup> {
+        return listOf<ViewGroup>(
+            cardView.requireViewById(R.id.media_cover1_container),
+            cardView.requireViewById(R.id.media_cover2_container),
+            cardView.requireViewById(R.id.media_cover3_container),
+        )
+    }
+
+    private fun getNumberOfFittedRecommendations(context: Context): Int {
+        val res = context.resources
+        val config = res.configuration
+        val defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp)
+        val recCoverWidth =
+            (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+                res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+
+        // On landscape, media controls should take half of the screen width.
+        val displayAvailableDpWidth =
+            if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                config.screenWidthDp / 2
+            } else {
+                config.screenWidthDp
+            }
+        val fittedNum =
+            if (displayAvailableDpWidth > defaultDpWidth) {
+                val recCoverDefaultWidth =
+                    res.getDimensionPixelSize(R.dimen.qs_media_rec_default_width)
+                recCoverDefaultWidth / recCoverWidth
+            } else {
+                val displayAvailableWidth =
+                    TypedValue.applyDimension(
+                            TypedValue.COMPLEX_UNIT_DIP,
+                            displayAvailableDpWidth.toFloat(),
+                            res.displayMetrics
+                        )
+                        .toInt()
+                displayAvailableWidth / recCoverWidth
+            }
+        return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 1a56a9b..bd3893b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -738,8 +738,11 @@
                                     mPackageName, mMediaViewHolder.getSeamlessButton());
                         } else {
                             mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
-                            mMediaOutputDialogManager.createAndShow(mPackageName, true,
-                                    mMediaViewHolder.getSeamlessButton());
+                            // TODO: b/321969740 - Populate the userHandle parameter. The user
+                            // handle is necessary to disambiguate the same package running on
+                            // different users.
+                            mMediaOutputDialogManager.createAndShow(
+                                    mPackageName, true, mMediaViewHolder.getSeamlessButton(), null);
                         }
                     } else {
                         mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
@@ -767,8 +770,11 @@
                                 Log.w(TAG, "Device pending intent is not an activity.");
                             }
                         } else {
-                            mMediaOutputDialogManager.createAndShow(mPackageName, true,
-                                    mMediaViewHolder.getSeamlessButton());
+                            // TODO: b/321969740 - Populate the userHandle parameter. The user
+                            // handle is necessary to disambiguate the same package running on
+                            // different users.
+                            mMediaOutputDialogManager.createAndShow(
+                                    mPackageName, true, mMediaViewHolder.getSeamlessButton(), null);
                         }
                     }
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index ad7990b..7fced5f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -16,41 +16,73 @@
 
 package com.android.systemui.media.controls.ui.controller
 
+import android.animation.Animator
+import android.animation.AnimatorInflater
+import android.animation.AnimatorSet
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.provider.Settings
+import android.view.View
+import android.view.animation.Interpolator
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import com.android.app.animation.Interpolators
 import com.android.app.tracing.traceSection
+import com.android.systemui.Flags
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
+import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler
+import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
 import com.android.systemui.media.controls.ui.view.GutsViewHolder
 import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.MeasurementOutput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionLayoutController
 import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.settings.GlobalSettings
 import java.lang.Float.max
 import java.lang.Float.min
+import java.util.Random
 import javax.inject.Inject
 
 /**
  * A class responsible for controlling a single instance of a media player handling interactions
  * with the view instance and keeping the media view states up to date.
  */
-class MediaViewController
+open class MediaViewController
 @Inject
 constructor(
     private val context: Context,
     private val configurationController: ConfigurationController,
     private val mediaHostStatesManager: MediaHostStatesManager,
     private val logger: MediaViewLogger,
+    private val seekBarViewModel: SeekBarViewModel,
+    @Main private val mainExecutor: DelayableExecutor,
     private val mediaFlags: MediaFlags,
+    private val globalSettings: GlobalSettings,
 ) {
 
     /**
@@ -69,6 +101,7 @@
     /** A listener when the current dimensions of the player change */
     lateinit var sizeChangedListener: () -> Unit
     lateinit var configurationChangeListener: () -> Unit
+    lateinit var recsConfigurationChangeListener: (MediaViewController, TransitionLayout) -> Unit
     private var firstRefresh: Boolean = true
     @VisibleForTesting private var transitionLayout: TransitionLayout? = null
     private val layoutController = TransitionLayoutController()
@@ -130,6 +163,72 @@
             return transitionLayout?.translationY ?: 0.0f
         }
 
+    /** Whether artwork is bound. */
+    var isArtworkBound: Boolean = false
+
+    /** previous background artwork */
+    var prevArtwork: Drawable? = null
+
+    /** Whether scrubbing time can show */
+    var canShowScrubbingTime: Boolean = false
+
+    /** Whether user is touching the seek bar to change the position */
+    var isScrubbing: Boolean = false
+
+    var isSeekBarEnabled: Boolean = false
+
+    /** Not visible value for previous button when scrubbing */
+    private var prevNotVisibleValue = ConstraintSet.GONE
+    private var isPrevButtonAvailable = false
+
+    /** Not visible value for next button when scrubbing */
+    private var nextNotVisibleValue = ConstraintSet.GONE
+    private var isNextButtonAvailable = false
+
+    private lateinit var mediaViewHolder: MediaViewHolder
+    private lateinit var seekBarObserver: SeekBarObserver
+    private lateinit var turbulenceNoiseController: TurbulenceNoiseController
+    private lateinit var loadingEffect: LoadingEffect
+    private lateinit var turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig
+    private lateinit var noiseDrawCallback: PaintDrawCallback
+    private lateinit var stateChangedCallback: LoadingEffect.AnimationStateChangedCallback
+    internal lateinit var metadataAnimationHandler: MetadataAnimationHandler
+    internal lateinit var colorSchemeTransition: ColorSchemeTransition
+    internal lateinit var multiRippleController: MultiRippleController
+
+    private val scrubbingChangeListener =
+        object : SeekBarViewModel.ScrubbingChangeListener {
+            override fun onScrubbingChanged(scrubbing: Boolean) {
+                if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+                if (isScrubbing == scrubbing) return
+                isScrubbing = scrubbing
+                updateDisplayForScrubbingChange()
+            }
+        }
+
+    private val enabledChangeListener =
+        object : SeekBarViewModel.EnabledChangeListener {
+            override fun onEnabledChanged(enabled: Boolean) {
+                if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+                if (isSeekBarEnabled == enabled) return
+                isSeekBarEnabled = enabled
+                MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
+            }
+        }
+
+    /**
+     * Sets the listening state of the player.
+     *
+     * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+     * unnecessary work when the QS panel is closed.
+     *
+     * @param listening True when player should be active. Otherwise, false.
+     */
+    fun setListening(listening: Boolean) {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        seekBarViewModel.listening = listening
+    }
+
     /** A callback for config changes */
     private val configurationListener =
         object : ConfigurationController.ConfigurationListener {
@@ -160,7 +259,17 @@
                             )
                         )
                     }
-                    if (this@MediaViewController::configurationChangeListener.isInitialized) {
+                    if (mediaFlags.isMediaControlsRefactorEnabled()) {
+                        if (
+                            this@MediaViewController::recsConfigurationChangeListener.isInitialized
+                        ) {
+                            transitionLayout?.let {
+                                recsConfigurationChangeListener.invoke(this@MediaViewController, it)
+                            }
+                        }
+                    } else if (
+                        this@MediaViewController::configurationChangeListener.isInitialized
+                    ) {
                         configurationChangeListener.invoke()
                         refreshState()
                     }
@@ -221,6 +330,14 @@
      * Notify this controller that the view has been removed and all listeners should be destroyed
      */
     fun onDestroy() {
+        if (mediaFlags.isMediaControlsRefactorEnabled()) {
+            if (this::seekBarObserver.isInitialized) {
+                seekBarViewModel.progress.removeObserver(seekBarObserver)
+            }
+            seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener)
+            seekBarViewModel.removeEnabledChangeListener(enabledChangeListener)
+            seekBarViewModel.onDestroy()
+        }
         mediaHostStatesManager.removeController(this)
         configurationController.removeCallback(configurationListener)
     }
@@ -535,6 +652,178 @@
             )
         }
 
+    fun attachPlayer(mediaViewHolder: MediaViewHolder) {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        this.mediaViewHolder = mediaViewHolder
+
+        // Setting up seek bar.
+        seekBarObserver = SeekBarObserver(mediaViewHolder)
+        seekBarViewModel.progress.observeForever(seekBarObserver)
+        seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar)
+        seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener)
+        seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
+
+        val mediaCard = mediaViewHolder.player
+        attach(mediaViewHolder.player, TYPE.PLAYER)
+
+        val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView
+        turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+        multiRippleController = MultiRippleController(mediaViewHolder.multiRippleView)
+
+        // Metadata Animation
+        val titleText = mediaViewHolder.titleText
+        val artistText = mediaViewHolder.artistText
+        val explicitIndicator = mediaViewHolder.explicitIndicator
+        val enter =
+            loadAnimator(
+                mediaCard.context,
+                R.anim.media_metadata_enter,
+                Interpolators.EMPHASIZED_DECELERATE,
+                titleText,
+                artistText,
+                explicitIndicator
+            )
+        val exit =
+            loadAnimator(
+                mediaCard.context,
+                R.anim.media_metadata_exit,
+                Interpolators.EMPHASIZED_ACCELERATE,
+                titleText,
+                artistText,
+                explicitIndicator
+            )
+        metadataAnimationHandler = MetadataAnimationHandler(exit, enter)
+
+        colorSchemeTransition =
+            ColorSchemeTransition(
+                mediaCard.context,
+                mediaViewHolder,
+                multiRippleController,
+                turbulenceNoiseController
+            )
+
+        // For Turbulence noise.
+        val loadingEffectView = mediaViewHolder.loadingEffectView
+        turbulenceNoiseAnimationConfig =
+            createTurbulenceNoiseConfig(
+                loadingEffectView,
+                turbulenceNoiseView,
+                colorSchemeTransition
+            )
+        noiseDrawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(paint: Paint) {
+                    loadingEffectView.draw(paint)
+                }
+            }
+        stateChangedCallback =
+            object : LoadingEffect.AnimationStateChangedCallback {
+                override fun onStateChanged(
+                    oldState: LoadingEffect.AnimationState,
+                    newState: LoadingEffect.AnimationState
+                ) {
+                    if (newState === LoadingEffect.AnimationState.NOT_PLAYING) {
+                        loadingEffectView.visibility = View.INVISIBLE
+                    } else {
+                        loadingEffectView.visibility = View.VISIBLE
+                    }
+                }
+            }
+    }
+
+    fun updateAnimatorDurationScale() {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (this::seekBarObserver.isInitialized) {
+            seekBarObserver.animationEnabled =
+                globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
+        }
+    }
+
+    /** update view with the needed UI changes when user touches seekbar. */
+    private fun updateDisplayForScrubbingChange() {
+        mainExecutor.execute {
+            val isTimeVisible = canShowScrubbingTime && isScrubbing
+            MediaControlViewBinder.setVisibleAndAlpha(
+                expandedLayout,
+                mediaViewHolder.scrubbingTotalTimeView.id,
+                isTimeVisible
+            )
+            MediaControlViewBinder.setVisibleAndAlpha(
+                expandedLayout,
+                mediaViewHolder.scrubbingElapsedTimeView.id,
+                isTimeVisible
+            )
+
+            MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id ->
+                val isButtonVisible: Boolean
+                val notVisibleValue: Int
+                when (id) {
+                    R.id.actionPrev -> {
+                        isButtonVisible = isPrevButtonAvailable && !isTimeVisible
+                        notVisibleValue = prevNotVisibleValue
+                    }
+                    R.id.actionNext -> {
+                        isButtonVisible = isNextButtonAvailable && !isTimeVisible
+                        notVisibleValue = nextNotVisibleValue
+                    }
+                    else -> {
+                        isButtonVisible = !isTimeVisible
+                        notVisibleValue = ConstraintSet.GONE
+                    }
+                }
+                MediaControlViewBinder.setSemanticButtonVisibleAndAlpha(
+                    mediaViewHolder.getAction(id),
+                    expandedLayout,
+                    collapsedLayout,
+                    isButtonVisible,
+                    notVisibleValue,
+                    showInCollapsed = true
+                )
+            }
+
+            if (!metadataAnimationHandler.isRunning) {
+                refreshState()
+            }
+        }
+    }
+
+    fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        seekBarViewModel.logSeek = onSeek
+        onBindSeekBar.invoke(seekBarViewModel)
+    }
+
+    fun setUpTurbulenceNoise() {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (Flags.shaderlibLoadingEffectRefactor()) {
+            if (!this::loadingEffect.isInitialized) {
+                loadingEffect =
+                    LoadingEffect(
+                        TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                        turbulenceNoiseAnimationConfig,
+                        noiseDrawCallback,
+                        stateChangedCallback
+                    )
+            }
+            colorSchemeTransition.loadingEffect = loadingEffect
+            loadingEffect.play()
+            mainExecutor.executeDelayed(
+                loadingEffect::finish,
+                MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+            )
+        } else {
+            turbulenceNoiseController.play(
+                TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                turbulenceNoiseAnimationConfig
+            )
+            mainExecutor.executeDelayed(
+                turbulenceNoiseController::finish,
+                MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+            )
+        }
+    }
+
     /**
      * Obtain a measurement for a given location. This makes sure that the state is up to date and
      * all widgets know their location. Calling this method may create a measurement if we don't
@@ -790,6 +1079,75 @@
                 applyImmediately = true
             )
         }
+
+    @VisibleForTesting
+    protected open fun loadAnimator(
+        context: Context,
+        animId: Int,
+        motionInterpolator: Interpolator?,
+        vararg targets: View?
+    ): AnimatorSet {
+        val animators = ArrayList<Animator>()
+        for (target in targets) {
+            val animator = AnimatorInflater.loadAnimator(context, animId) as AnimatorSet
+            animator.childAnimations[0].interpolator = motionInterpolator
+            animator.setTarget(target)
+            animators.add(animator)
+        }
+        val result = AnimatorSet()
+        result.playTogether(animators)
+        return result
+    }
+
+    private fun createTurbulenceNoiseConfig(
+        loadingEffectView: LoadingEffectView,
+        turbulenceNoiseView: TurbulenceNoiseView,
+        colorSchemeTransition: ColorSchemeTransition
+    ): TurbulenceNoiseAnimationConfig {
+        val targetView: View =
+            if (Flags.shaderlibLoadingEffectRefactor()) {
+                loadingEffectView
+            } else {
+                turbulenceNoiseView
+            }
+        val width = targetView.width
+        val height = targetView.height
+        val random = Random()
+        return TurbulenceNoiseAnimationConfig(
+            gridCount = 2.14f,
+            TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+            random.nextFloat(),
+            random.nextFloat(),
+            random.nextFloat(),
+            noiseMoveSpeedX = 0.42f,
+            noiseMoveSpeedY = 0f,
+            TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+            // Color will be correctly updated in ColorSchemeTransition.
+            colorSchemeTransition.accentPrimary.currentColor,
+            screenColor = Color.BLACK,
+            width.toFloat(),
+            height.toFloat(),
+            TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
+            easeInDuration = 1350f,
+            easeOutDuration = 1350f,
+            targetView.context.resources.displayMetrics.density,
+            lumaMatteBlendFactor = 0.26f,
+            lumaMatteOverallBrightness = 0.09f,
+            shouldInverseNoiseLuminosity = false
+        )
+    }
+
+    fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        isPrevButtonAvailable = isAvailable
+        prevNotVisibleValue = notVisibleValue
+    }
+
+    fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
+        if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        isNextButtonAvailable = isAvailable
+        nextNotVisibleValue = notVisibleValue
+    }
 }
 
 /** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
index e508e1b..6c7c31c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
@@ -22,9 +22,9 @@
 /** Models UI state for media guts menu */
 data class GutsViewModel(
     val gutsText: CharSequence,
-    @ColorInt val textColor: Int,
-    @ColorInt val buttonBackgroundColor: Int,
-    @ColorInt val buttonTextColor: Int,
+    @ColorInt val textPrimaryColor: Int,
+    @ColorInt val accentPrimaryColor: Int,
+    @ColorInt val surfaceColor: Int,
     val isDismissEnabled: Boolean = true,
     val onDismissClicked: () -> Unit,
     val cancelTextBackground: Drawable?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
index 1e67a77..82099e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
@@ -24,7 +24,8 @@
     val icon: Drawable?,
     val contentDescription: CharSequence?,
     val background: Drawable?,
-    val isVisible: Boolean = true,
+    /** whether action is visible if user is touching seekbar to change position. */
+    val isVisibleWhenScrubbing: Boolean = true,
     val notVisibleValue: Int = ConstraintSet.GONE,
     val showInCollapsed: Boolean,
     val rebindId: Int? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 117b2af..d74506d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.pm.PackageManager
+import android.media.session.MediaController
 import android.media.session.MediaSession.Token
 import android.text.TextUtils
 import android.util.Log
@@ -40,6 +41,7 @@
 import com.android.systemui.monet.Style
 import com.android.systemui.res.R
 import com.android.systemui.util.kotlin.sample
+import java.util.concurrent.Executor
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,6 +53,7 @@
 class MediaControlViewModel(
     @Application private val applicationContext: Context,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val backgroundExecutor: Executor,
     private val interactor: MediaControlInteractor,
     private val logger: MediaUiEventLogger,
 ) {
@@ -124,13 +127,15 @@
                 }
             },
             backgroundCover = model.artwork,
-            appIcon = getAppIcon(model.appIcon, model.isResume, model.packageName),
+            appIcon = model.appIcon,
+            launcherIcon = getIconFromApp(model.packageName),
             useGrayColorFilter = model.appIcon == null || model.isResume,
             artistName = model.artistName ?: "",
             titleName = model.songName ?: "",
             isExplicitVisible = model.showExplicit,
+            shouldAddGradient = wallpaperColors != null,
             colorScheme = scheme,
-            isTimeVisible = canShowScrubbingTimeViews(model.semanticActionButtons),
+            canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons),
             playTurbulenceNoise = playTurbulenceNoise,
             useSemanticActions = model.semanticActionButtons != null,
             actionButtons = toActionViewModels(model),
@@ -146,6 +151,21 @@
             onLongClicked = {
                 logger.logLongPressOpen(model.uid, model.packageName, model.instanceId)
             },
+            onSeek = {
+                logger.logSeek(model.uid, model.packageName, model.instanceId)
+                // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT)
+            },
+            onBindSeekbar = { seekBarViewModel ->
+                if (model.isResume && model.resumeProgress != null) {
+                    seekBarViewModel.updateStaticProgress(model.resumeProgress)
+                } else {
+                    backgroundExecutor.execute {
+                        seekBarViewModel.updateController(
+                            model.token?.let { MediaController(applicationContext, it) }
+                        )
+                    }
+                }
+            }
         )
     }
 
@@ -235,9 +255,9 @@
                 } else {
                     applicationContext.getString(R.string.controls_media_active_session)
                 },
-            textColor = textPrimaryFromScheme(scheme),
-            buttonBackgroundColor = accentPrimaryFromScheme(scheme),
-            buttonTextColor = surfaceFromScheme(scheme),
+            textPrimaryColor = textPrimaryFromScheme(scheme),
+            accentPrimaryColor = accentPrimaryFromScheme(scheme),
+            surfaceColor = surfaceFromScheme(scheme),
             isDismissEnabled = model.isDismissible,
             onDismissClicked = {
                 onDismissMediaData(model.token, model.uid, model.packageName, model.instanceId)
@@ -278,16 +298,16 @@
         model: MediaControlModel,
         mediaAction: MediaAction,
         buttonId: Int,
-        isScrubbingTimeEnabled: Boolean
+        canShowScrubbingTimeViews: Boolean
     ): MediaActionViewModel {
         val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId)
         val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
-        val shouldHideDueToScrubbing = isScrubbingTimeEnabled && hideWhenScrubbing
+        val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing
         return MediaActionViewModel(
             icon = mediaAction.icon,
             contentDescription = mediaAction.contentDescription,
             background = mediaAction.background,
-            isVisible = !shouldHideDueToScrubbing,
+            isVisibleWhenScrubbing = !shouldHideWhenScrubbing,
             notVisibleValue =
                 if (
                     (buttonId == R.id.actionPrev && model.semanticActionButtons!!.reservePrev) ||
@@ -342,19 +362,6 @@
         action.run()
     }
 
-    private fun getAppIcon(
-        icon: android.graphics.drawable.Icon?,
-        isResume: Boolean,
-        packageName: String
-    ): Icon {
-        if (icon != null && !isResume) {
-            icon.loadDrawable(applicationContext)?.let { drawable ->
-                return Icon.Loaded(drawable, null)
-            }
-        }
-        return getIconFromApp(packageName)
-    }
-
     private fun getIconFromApp(packageName: String): Icon {
         return try {
             Icon.Loaded(applicationContext.packageManager.getApplicationIcon(packageName), null)
@@ -381,17 +388,17 @@
         private const val DISABLED_ALPHA = 0.38f
 
         /** Buttons to show in small player when using semantic actions */
-        private val SEMANTIC_ACTIONS_COMPACT =
+        val SEMANTIC_ACTIONS_COMPACT =
             listOf(R.id.actionPlayPause, R.id.actionPrev, R.id.actionNext)
 
         /**
          * Buttons that should get hidden when we are scrubbing (they will be replaced with the
          * views showing scrubbing time)
          */
-        private val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext)
+        val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext)
 
         /** Buttons to show in player when using semantic actions. */
-        private val SEMANTIC_ACTIONS_ALL =
+        val SEMANTIC_ACTIONS_ALL =
             listOf(
                 R.id.actionPlayPause,
                 R.id.actionPrev,
@@ -399,5 +406,9 @@
                 R.id.action0,
                 R.id.action1
             )
+
+        const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
+        const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f
+        const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index 9029a65..d1014e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -24,13 +24,15 @@
 data class MediaPlayerViewModel(
     val contentDescription: (Boolean) -> CharSequence,
     val backgroundCover: android.graphics.drawable.Icon?,
-    val appIcon: Icon,
+    val appIcon: android.graphics.drawable.Icon?,
+    val launcherIcon: Icon,
     val useGrayColorFilter: Boolean,
     val artistName: CharSequence,
     val titleName: CharSequence,
     val isExplicitVisible: Boolean,
+    val shouldAddGradient: Boolean,
     val colorScheme: ColorScheme,
-    val isTimeVisible: Boolean,
+    val canShowTime: Boolean,
     val playTurbulenceNoise: Boolean,
     val useSemanticActions: Boolean,
     val actionButtons: List<MediaActionViewModel?>,
@@ -38,4 +40,6 @@
     val gutsMenu: GutsViewModel,
     val onClicked: (Expandable) -> Unit,
     val onLongClicked: () -> Unit,
+    val onSeek: () -> Unit,
+    val onBindSeekbar: (SeekBarViewModel) -> Unit,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
index b0375f0..a2307d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -213,9 +213,9 @@
         return GutsViewModel(
             gutsText =
                 applicationContext.getString(R.string.controls_media_close_session, model.appName),
-            textColor = textPrimaryFromScheme(scheme),
-            buttonBackgroundColor = accentPrimaryFromScheme(scheme),
-            buttonTextColor = surfaceFromScheme(scheme),
+            textPrimaryColor = textPrimaryFromScheme(scheme),
+            accentPrimaryColor = accentPrimaryFromScheme(scheme),
+            surfaceColor = surfaceFromScheme(scheme),
             onDismissClicked = {
                 onMediaRecommendationsDismissed(
                     model.key,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 452cb7e..ff8e903b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,8 +31,9 @@
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
     fun create(packageName: String?): LocalMediaManager {
-        return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
-            LocalMediaManager(context, localBluetoothManager, this, packageName)
-        }
+        // TODO: b/321969740 - Populate the userHandle parameter in InfoMediaManager. The user
+        // handle is necessary to disambiguate the same package running on different users.
+        return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+            .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4bd6da..4e77d13 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -21,16 +21,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import javax.inject.Inject
 
 @SysUISingleton
-class MediaFlags
-@Inject
-constructor(
-    private val featureFlags: FeatureFlagsClassic,
-    private val sceneContainerFlags: SceneContainerFlags
-) {
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClassic) {
     /**
      * Check whether media control actions should be based on PlaybackState instead of notification
      */
@@ -57,7 +52,7 @@
 
     /** Check whether to use scene framework */
     fun isSceneContainerEnabled() =
-        sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
+        SceneContainerFlag.isEnabled && MediaInSceneContainerFlag.isEnabled
 
     /** Check whether to use media refactor code */
     fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 54d175c..06267e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -38,7 +38,9 @@
         // Dismiss the previous dialog, if any.
         mediaOutputBroadcastDialog?.dismiss()
 
-        val controller = mediaOutputControllerFactory.create(packageName)
+        // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
+        //  disambiguate the same package running on different users.
+        val controller = mediaOutputControllerFactory.create(packageName, /* userHandle= */ null)
         val dialog =
             MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
         mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 4db89d1..d6ca320 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -124,6 +124,7 @@
     private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer";
 
     private final String mPackageName;
+    private final UserHandle mUserHandle;
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
     private final LocalBluetoothManager mLocalBluetoothManager;
@@ -177,6 +178,7 @@
     public MediaOutputController(
             Context context,
             @Assisted String packageName,
+            @Assisted @Nullable UserHandle userHandle,
             MediaSessionManager mediaSessionManager,
             @Nullable LocalBluetoothManager lbm,
             ActivityStarter starter,
@@ -190,6 +192,7 @@
             UserTracker userTracker) {
         mContext = context;
         mPackageName = packageName;
+        mUserHandle = userHandle;
         mMediaSessionManager = mediaSessionManager;
         mLocalBluetoothManager = lbm;
         mActivityStarter = starter;
@@ -199,7 +202,8 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
-        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
+        InfoMediaManager imm =
+                InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -231,7 +235,7 @@
     @AssistedFactory
     public interface Factory {
         /** Construct a MediaOutputController */
-        MediaOutputController create(String packageName);
+        MediaOutputController create(String packageName, UserHandle userHandle);
     }
 
     protected void start(@NonNull Callback cb) {
@@ -946,11 +950,22 @@
     }
 
     void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
-        MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
-                mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
-                mNotifCollection, mDialogTransitionAnimator, mNearbyMediaDevicesManager,
-                mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
-                mUserTracker);
+        MediaOutputController controller =
+                new MediaOutputController(
+                        mContext,
+                        mPackageName,
+                        mUserHandle,
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mActivityStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyGuardManager,
+                        mFeatureFlags,
+                        mUserTracker);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
                 broadcastSender, controller);
         dialog.show();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index e7816a4..04d1492 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.dialog
 
 import android.content.Context
+import android.os.UserHandle
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
@@ -41,7 +42,15 @@
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
-    open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+    // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+    // package name. The user handle is necessary to disambiguate the same package running on
+    // different users.
+    open fun createAndShow(
+        packageName: String,
+        aboveStatusBar: Boolean,
+        view: View? = null,
+        userHandle: UserHandle? = null
+    ) {
         createAndShowWithController(
             packageName,
             aboveStatusBar,
@@ -55,20 +64,26 @@
                         )
                     )
                 },
+            userHandle = userHandle,
         )
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
+    // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+    // package name. The user handle is necessary to disambiguate the same package running on
+    // different users.
     open fun createAndShowWithController(
         packageName: String,
         aboveStatusBar: Boolean,
         controller: DialogTransitionAnimator.Controller?,
+        userHandle: UserHandle? = null,
     ) {
         createAndShow(
             packageName,
             aboveStatusBar,
             dialogTransitionAnimatorController = controller,
-            includePlaybackAndAppMetadata = true
+            includePlaybackAndAppMetadata = true,
+            userHandle = userHandle,
         )
     }
 
@@ -79,20 +94,25 @@
             packageName = null,
             aboveStatusBar = false,
             dialogTransitionAnimatorController = null,
-            includePlaybackAndAppMetadata = false
+            includePlaybackAndAppMetadata = false,
+            userHandle = null,
         )
     }
 
+    // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+    // package name. The user handle is necessary to disambiguate the same package running on
+    // different users.
     private fun createAndShow(
         packageName: String?,
         aboveStatusBar: Boolean,
         dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
-        includePlaybackAndAppMetadata: Boolean = true
+        includePlaybackAndAppMetadata: Boolean = true,
+        userHandle: UserHandle? = null,
     ) {
         // Dismiss the previous dialog, if any.
         mediaOutputDialog?.dismiss()
 
-        val controller = mediaOutputControllerFactory.create(packageName)
+        val controller = mediaOutputControllerFactory.create(packageName, userHandle)
 
         val mediaOutputDialog =
             MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index da85234..9cc2888 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -55,8 +55,8 @@
     @MainThread
     public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
         if (!TextUtils.isEmpty(packageName)) {
-            // TODO: b/279555229 - Pass the userHandle into the output dialog manager.
-            mMediaOutputDialogManager.createAndShow(packageName, false, null);
+            mMediaOutputDialogManager.createAndShow(
+                    packageName, /* aboveStatusBar= */ false, /* view= */ null, userHandle);
         } else {
             Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
index 9514c4a..b7942f7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
@@ -27,8 +27,8 @@
 import android.util.Log
 import android.view.SurfaceControl
 import android.view.animation.DecelerateInterpolator
-import android.window.IRemoteTransition
 import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransitionStub
 import android.window.TransitionInfo
 import android.window.WindowContainerToken
 import com.android.app.viewcapture.ViewCapture
@@ -41,7 +41,7 @@
     private val viewPosition: IntArray,
     private val screenBounds: Rect,
     private val handleResult: () -> Unit,
-) : IRemoteTransition.Stub() {
+) : RemoteTransitionStub() {
     override fun startAnimation(
         transition: IBinder?,
         info: TransitionInfo?,
@@ -114,14 +114,6 @@
         }
     }
 
-    override fun mergeAnimation(
-        transition: IBinder?,
-        info: TransitionInfo?,
-        t: SurfaceControl.Transaction?,
-        mergeTarget: IBinder?,
-        finishedCallback: IRemoteTransitionFinishedCallback?
-    ) {}
-
     @Throws(RemoteException::class)
     override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) {
         Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted")
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 63989ef..a6b6d61 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -35,6 +35,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
@@ -74,6 +75,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Lazy;
@@ -101,7 +103,7 @@
         AccessibilityButtonModeObserver.ModeChangedListener,
         AccessibilityButtonTargetsObserver.TargetsChangedListener,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
-        Dumpable, CommandQueue.Callbacks {
+        Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
     private static final String TAG = NavBarHelper.class.getSimpleName();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -189,6 +191,7 @@
             UserTracker userTracker,
             DisplayTracker displayTracker,
             NotificationShadeWindowController notificationShadeWindowController,
+            ConfigurationController configurationController,
             DumpManager dumpManager,
             CommandQueue commandQueue,
             @Main Executor mainExecutor) {
@@ -215,6 +218,7 @@
 
         mNavBarMode = navigationModeController.addListener(this);
         mCommandQueue.addCallback(this);
+        configurationController.addCallback(this);
         overviewProxyService.addCallback(this);
         dumpManager.registerDumpable(this);
     }
@@ -359,6 +363,11 @@
         updateA11yState();
     }
 
+    @Override
+    public void onConfigChanged(Configuration newConfig) {
+        mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
+    }
+
     /**
      * Updates the current accessibility button state. The accessibility button state is only
      * used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index ade56c4..b609864 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -902,11 +902,6 @@
             refreshLayout(ld);
         }
         repositionNavigationBar(rotation);
-        // NOTE(b/260220098): In some cases, the recreated nav bar will already have the right
-        // configuration, which means that NavBarView will not receive a configuration change to
-        // propagate to EdgeBackGestureHandler (which is injected into this and NBV). As such, we
-        // should also force-update the gesture handler to ensure it updates to the right bounds
-        mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
         if (canShowSecondaryHandle()) {
             if (rotation != mCurrentRotation) {
                 mCurrentRotation = rotation;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 9f7d1b3..12f2703 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -162,14 +162,11 @@
         mIsLargeScreen = isLargeScreen(mContext);
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
         boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
-        // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+        // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
         Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
                 + " willApplyConfigToNavbars=" + willApplyConfig
                 + " navBarCount=" + mNavigationBars.size());
-        if (mTaskbarDelegate.isInitialized()) {
-            mTaskbarDelegate.onConfigurationChanged(newConfig);
-        }
         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
         if (largeScreenChanged && updateNavbarForTaskbar()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 1927f49..3c69ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1031,7 +1031,6 @@
         updateIcons(mTmpLastConfiguration);
         updateRecentsIcon();
         updateCurrentRotation();
-        mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration);
         if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
                 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
             // If car mode or density changes, we need to reset the icons.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 5dd1bd8..f67973b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -39,7 +39,6 @@
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.WindowVisibleState;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
@@ -72,7 +71,6 @@
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -250,8 +248,6 @@
             mLightBarController.setNavigationBar(mLightBarTransitionsController);
             mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
             mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
-            mEdgeBackGestureHandler.onConfigurationChanged(
-                    mContext.getResources().getConfiguration());
             mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
             mInitialized = true;
         } finally {
@@ -495,10 +491,6 @@
         return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
     }
 
-    public void onConfigurationChanged(Configuration configuration) {
-        mEdgeBackGestureHandler.onConfigurationChanged(configuration);
-    }
-
     @Override
     public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
         mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9d0ea5e..db4a7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1187,7 +1187,7 @@
             updateDisabledForQuickstep(newConfig);
         }
 
-        // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+        // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
         Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
                 + " lastReportedConfig=" + mLastReportedConfig);
         mLastReportedConfig.updateFrom(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index ffbc560..7ccdf0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -24,7 +24,7 @@
 
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
 
@@ -66,15 +66,14 @@
             QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
             ConfigurationController configurationController,
-            FalsingManager falsingManager,
-            SceneContainerFlags sceneContainerFlags) {
+            FalsingManager falsingManager) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
         mFalsingManager = falsingManager;
         mQSPanelContainer = mView.getQSPanelContainer();
-        mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+        mSceneContainerEnabled = SceneContainerFlag.isEnabled();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a0607e9..1f4838e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -60,7 +60,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.settings.brightness.MirrorController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
@@ -174,8 +174,6 @@
     @Nullable
     private View mFooterActionsView;
 
-    private final SceneContainerFlags mSceneContainerFlags;
-
     @Inject
     public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -188,8 +186,7 @@
             FooterActionsViewModel.Factory footerActionsViewModelFactory,
             FooterActionsViewBinder footerActionsViewBinder,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            FeatureFlags featureFlags,
-            SceneContainerFlags sceneContainerFlags) {
+            FeatureFlags featureFlags) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mQsMediaHost = qsMediaHost;
         mQqsMediaHost = qqsMediaHost;
@@ -205,8 +202,7 @@
         mFooterActionsViewModelFactory = footerActionsViewModelFactory;
         mFooterActionsViewBinder = footerActionsViewBinder;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
-        mSceneContainerFlags = sceneContainerFlags;
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             mStatusBarState = StatusBarState.SHADE;
         }
     }
@@ -224,7 +220,7 @@
         mQSPanelController.init();
         mQuickQSPanelController.init();
 
-        if (!mSceneContainerFlags.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             mQSFooterActionsViewModel = mFooterActionsViewModelFactory
                     .create(mListeningAndVisibilityLifecycleOwner);
             bindFooterActionsView(mRootView);
@@ -249,7 +245,7 @@
                         mScrollListener.onQsPanelScrollChanged(scrollY);
                     }
                 });
-        mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
+        mQSPanelScrollView.setScrollingEnabled(!SceneContainerFlag.isEnabled());
         mHeader = mRootView.findViewById(R.id.header);
         mFooter = qsComponent.getQSFooter();
 
@@ -509,7 +505,7 @@
 
     @VisibleForTesting
     boolean isKeyguardState() {
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             return false;
         } else {
             // We want the freshest state here since otherwise we'll have some weirdness if earlier
@@ -573,7 +569,7 @@
     }
 
     private void setKeyguardShowing(boolean keyguardShowing) {
-        if (!mSceneContainerFlags.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
             mLastQSExpansion = -1;
 
@@ -651,7 +647,7 @@
 
     @Override
     public int getHeightDiff() {
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             return mQSPanelController.getViewBottom() - mHeader.getBottom()
                     + mHeader.getPaddingBottom();
         } else {
@@ -720,7 +716,7 @@
         mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
         mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
 
-        if (!mSceneContainerFlags.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             float qsScrollViewTranslation =
                     onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
             mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
@@ -824,7 +820,7 @@
             mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
                     mQSPanelScrollView.getHeight());
         }
-        if (!mSceneContainerFlags.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             mQSPanelScrollView.setClipBounds(mQsBounds);
 
             mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
@@ -907,7 +903,7 @@
         // The customize state changed, so our height changed.
         mContainer.updateExpansion();
         boolean customizing = isCustomizing();
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         } else {
             mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
@@ -984,7 +980,7 @@
 
     @Override
     public void onStateChanged(int newState) {
-        if (mSceneContainerFlags.isEnabled() || newState == mStatusBarState) {
+        if (SceneContainerFlag.isEnabled() || newState == mStatusBarState) {
             return;
         }
         mStatusBarState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b8c3c1a..e24caf1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -37,7 +37,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -94,7 +94,6 @@
             FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             SplitShadeStateController splitShadeStateController,
-            SceneContainerFlags sceneContainerFlags,
             Provider<QSLongPressEffect> longPRessEffectProvider) {
         super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
@@ -113,7 +112,7 @@
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLastDensity = view.getResources().getConfiguration().densityDpi;
-        mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+        mSceneContainerEnabled = SceneContainerFlag.isEnabled();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 1d92d78..bb40d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -17,7 +17,7 @@
 package com.android.systemui.qs;
 
 import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -34,12 +34,11 @@
 
     @Inject
     QuickStatusBarHeaderController(QuickStatusBarHeader view,
-            QuickQSPanelController quickQSPanelController,
-            SceneContainerFlags sceneContainerFlags
+            QuickQSPanelController quickQSPanelController
     ) {
         super(view);
         mQuickQSPanelController = quickQSPanelController;
-        mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+        mSceneContainerEnabled = SceneContainerFlag.isEnabled();
     }
     @Override
     protected void onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index a01d658..10c8e53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,124 +16,21 @@
 
 package com.android.systemui.qs;
 
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.display.ColorDisplayManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-/**
- * @hide
- */
-@SysUISingleton
-public class ReduceBrightColorsController implements
+public interface ReduceBrightColorsController extends
         CallbackController<ReduceBrightColorsController.Listener> {
-    private final ColorDisplayManager mManager;
-    private final UserTracker mUserTracker;
-    private UserTracker.Callback mCurrentUserTrackerCallback;
-    private final Handler mHandler;
-    private final ContentObserver mContentObserver;
-    private final SecureSettings mSecureSettings;
-    private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
-
-    @Inject
-    public ReduceBrightColorsController(UserTracker userTracker,
-            @Background Handler handler,
-            ColorDisplayManager colorDisplayManager,
-            SecureSettings secureSettings) {
-        mManager = colorDisplayManager;
-        mUserTracker = userTracker;
-        mHandler = handler;
-        mSecureSettings = secureSettings;
-        mContentObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                super.onChange(selfChange, uri);
-                final String setting = uri == null ? null : uri.getLastPathSegment();
-                synchronized (mListeners) {
-                    if (setting != null && mListeners.size() != 0) {
-                        if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
-                            dispatchOnActivated(mManager.isReduceBrightColorsActivated());
-                        }
-                    }
-                }
-            }
-        };
-
-        mCurrentUserTrackerCallback = new UserTracker.Callback() {
-            @Override
-            public void onUserChanged(int newUser, Context userContext) {
-                synchronized (mListeners) {
-                    if (mListeners.size() > 0) {
-                        mSecureSettings.unregisterContentObserver(mContentObserver);
-                        mSecureSettings.registerContentObserverForUser(
-                                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
-                                false, mContentObserver, newUser);
-                    }
-                }
-            }
-        };
-        mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
-    }
-
-    @Override
-    public void addCallback(@NonNull Listener listener) {
-        synchronized (mListeners) {
-            if (!mListeners.contains(listener)) {
-                mListeners.add(listener);
-                if (mListeners.size() == 1) {
-                    mSecureSettings.registerContentObserverForUser(
-                            Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
-                            false, mContentObserver, mUserTracker.getUserId());
-                }
-            }
-        }
-    }
-
-    @Override
-    public void removeCallback(@androidx.annotation.NonNull Listener listener) {
-        synchronized (mListeners) {
-            if (mListeners.remove(listener) && mListeners.size() == 0) {
-                mSecureSettings.unregisterContentObserver(mContentObserver);
-            }
-        }
-    }
 
     /** Returns {@code true} if Reduce Bright Colors is activated */
-    public boolean isReduceBrightColorsActivated() {
-        return mManager.isReduceBrightColorsActivated();
-    }
+    boolean isReduceBrightColorsActivated();
 
     /** Sets the activation state of Reduce Bright Colors */
-    public void setReduceBrightColorsActivated(boolean activated) {
-        mManager.setReduceBrightColorsActivated(activated);
-    }
-
-    private void dispatchOnActivated(boolean activated) {
-        ArrayList<Listener> copy = new ArrayList<>(mListeners);
-        for (Listener l : copy) {
-            l.onActivated(activated);
-        }
-    }
+    void setReduceBrightColorsActivated(boolean activated);
 
     /**
      * Listener invoked whenever the Reduce Bright Colors settings are changed.
      */
-    public interface Listener {
+    interface Listener {
         /**
          * Listener invoked when the activated state changes.
          *
@@ -142,4 +39,4 @@
         default void onActivated(boolean activated) {
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
new file mode 100644
index 0000000..4fc6609
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @hide
+ */
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+@SysUISingleton
+public class ReduceBrightColorsControllerImpl implements
+        ReduceBrightColorsController {
+    private final ColorDisplayManager mManager;
+    private final UserTracker mUserTracker;
+    private UserTracker.Callback mCurrentUserTrackerCallback;
+    private final Handler mHandler;
+    private final ContentObserver mContentObserver;
+    private final SecureSettings mSecureSettings;
+    private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
+
+    @Inject
+    public ReduceBrightColorsControllerImpl(UserTracker userTracker,
+            @Background Handler handler,
+            ColorDisplayManager colorDisplayManager,
+            SecureSettings secureSettings) {
+        mManager = colorDisplayManager;
+        mUserTracker = userTracker;
+        mHandler = handler;
+        mSecureSettings = secureSettings;
+        mContentObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                final String setting = uri == null ? null : uri.getLastPathSegment();
+                synchronized (mListeners) {
+                    if (setting != null && mListeners.size() != 0) {
+                        if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
+                            dispatchOnActivated(mManager.isReduceBrightColorsActivated());
+                        }
+                    }
+                }
+            }
+        };
+
+        mCurrentUserTrackerCallback = new UserTracker.Callback() {
+            @Override
+            public void onUserChanged(int newUser, Context userContext) {
+                synchronized (mListeners) {
+                    if (mListeners.size() > 0) {
+                        mSecureSettings.unregisterContentObserver(mContentObserver);
+                        mSecureSettings.registerContentObserverForUser(
+                                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+                                false, mContentObserver, newUser);
+                    }
+                }
+            }
+        };
+        mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
+    }
+
+    @Override
+    public void addCallback(@NonNull Listener listener) {
+        synchronized (mListeners) {
+            if (!mListeners.contains(listener)) {
+                mListeners.add(listener);
+                if (mListeners.size() == 1) {
+                    mSecureSettings.registerContentObserverForUser(
+                            Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+                            false, mContentObserver, mUserTracker.getUserId());
+                }
+            }
+        }
+    }
+
+    @Override
+    public void removeCallback(@androidx.annotation.NonNull Listener listener) {
+        synchronized (mListeners) {
+            if (mListeners.remove(listener) && mListeners.size() == 0) {
+                mSecureSettings.unregisterContentObserver(mContentObserver);
+            }
+        }
+    }
+
+    @Override
+    public boolean isReduceBrightColorsActivated() {
+        return mManager.isReduceBrightColorsActivated();
+    }
+
+    @Override
+    public void setReduceBrightColorsActivated(boolean activated) {
+        mManager.setReduceBrightColorsActivated(activated);
+    }
+
+    private void dispatchOnActivated(boolean activated) {
+        ArrayList<Listener> copy = new ArrayList<>(mListeners);
+        for (Listener l : copy) {
+            l.onActivated(activated);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 34b1b2d..a222b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -42,7 +42,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -107,8 +107,7 @@
     protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
             QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
             KeyguardStateController keyguardStateController, LightBarController lightBarController,
-            ConfigurationController configurationController, UiEventLogger uiEventLogger,
-            SceneContainerFlags sceneContainerFlags) {
+            ConfigurationController configurationController, UiEventLogger uiEventLogger) {
         super(view);
         mTileQueryHelper = tileQueryHelper;
         mQsHost = qsHost;
@@ -118,7 +117,7 @@
         mLightBarController = lightBarController;
         mConfigurationController = configurationController;
         mUiEventLogger = uiEventLogger;
-        view.setSceneContainerEnabled(sceneContainerFlags.isEnabled());
+        view.setSceneContainerEnabled(SceneContainerFlag.isEnabled());
 
         mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 8d3500a..b705a03 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -28,6 +28,7 @@
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.panels.dagger.PanelsModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
@@ -118,4 +119,11 @@
 
     @Binds
     QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
+
+    /**
+     * Dims the screen
+     */
+    @Binds
+    ReduceBrightColorsController bindReduceBrightColorsController(
+            ReduceBrightColorsControllerImpl impl);
 }
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 6c9a8a4..5122e1f 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
@@ -28,6 +28,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -57,7 +58,9 @@
         val viewModel: QSTileViewModel =
             when (val spec = TileSpec.create(tileSpec)) {
                 is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
-                is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
+                // when using the stub, we default to old tile rather than adding the stub
+                is TileSpec.PlatformTileSpec ->
+                    tileMap[tileSpec]?.get()?.takeIf { it !is StubQSTileViewModel }
                 is TileSpec.Invalid -> null
             }
                 ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
new file mode 100644
index 0000000..98fd561
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.util.kotlin.isEnabled
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/** Observes reduce bright colors state changes providing the [ReduceBrightColorsTileModel]. */
+class ReduceBrightColorsTileDataInteractor
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Named(RBC_AVAILABLE) private val isAvailable: Boolean,
+    private val reduceBrightColorsController: ReduceBrightColorsController,
+) : QSTileDataInteractor<ReduceBrightColorsTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<ReduceBrightColorsTileModel> {
+        return reduceBrightColorsController
+            .isEnabled()
+            .distinctUntilChanged()
+            .map { ReduceBrightColorsTileModel(it) }
+            .flowOn(bgCoroutineContext)
+    }
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
new file mode 100644
index 0000000..762f863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles reduce bright colors tile clicks. */
+class ReduceBrightColorsTileUserActionInteractor
+@Inject
+constructor(
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val reduceBrightColorsController: ReduceBrightColorsController,
+) : QSTileUserActionInteractor<ReduceBrightColorsTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<ReduceBrightColorsTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    reduceBrightColorsController.setReduceBrightColorsActivated(
+                        !input.data.isEnabled
+                    )
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
index 979d8e7..05e0f5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.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.
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.model
 
-import com.android.systemui.kosmos.Kosmos
-
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+/**
+ * Reduce bright colors tile model.
+ *
+ * @param isEnabled is true when the reduce bright colors is enabled;
+ */
+@JvmInline value class ReduceBrightColorsTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
new file mode 100644
index 0000000..fca93df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
+class ReduceBrightColorsTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+
+    override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                icon = {
+                    Icon.Loaded(
+                        drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_on, theme),
+                        contentDescription = null
+                    )
+                }
+
+                secondaryLabel =
+                    resources
+                        .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_ACTIVE]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                icon = {
+                    Icon.Loaded(
+                        drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_off, theme),
+                        contentDescription = null
+                    )
+                }
+                secondaryLabel =
+                    resources
+                        .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
+            }
+            label =
+                resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+            contentDescription = label
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
new file mode 100644
index 0000000..64f1fd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+
+object StubQSTileViewModel : QSTileViewModel {
+
+    override val state: SharedFlow<QSTileState>
+        get() = error("Don't call stubs")
+
+    override val config: QSTileConfig
+        get() = error("Don't call stubs")
+
+    override val isAvailable: StateFlow<Boolean>
+        get() = error("Don't call stubs")
+
+    override fun onUserChanged(user: UserHandle) = error("Don't call stubs")
+
+    override fun forceUpdate() = error("Don't call stubs")
+
+    override fun onActionPerformed(userAction: QSTileUserAction) = error("Don't call stubs")
+
+    override fun destroy() = error("Don't call stubs")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4ece7b6..1ddc094 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -98,7 +98,7 @@
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -146,7 +146,6 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
-    private final SceneContainerFlags mSceneContainerFlags;
     private final Executor mMainExecutor;
     private final ShellInterface mShellInterface;
     private final Lazy<ShadeViewController> mShadeViewControllerLazy;
@@ -208,7 +207,7 @@
         @Override
         public void onStatusBarTouchEvent(MotionEvent event) {
             verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> {
-                if (mSceneContainerFlags.isEnabled()) {
+                if (SceneContainerFlag.isEnabled()) {
                     //TODO(b/329863123) implement latency tracking for shade scene
                     Log.i(TAG_OPS, "Scene container enabled. Latency tracking not started.");
                 } else if (event.getActionMasked() == ACTION_DOWN) {
@@ -223,7 +222,7 @@
 
                         // If scene framework is enabled, set the scene container window to
                         // visible and let the touch "slip" into that window.
-                        if (mSceneContainerFlags.isEnabled()) {
+                        if (SceneContainerFlag.isEnabled()) {
                             mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
                         } else {
                             mShadeViewControllerLazy.get().startInputFocusTransfer();
@@ -232,7 +231,7 @@
                     if (action == ACTION_UP || action == ACTION_CANCEL) {
                         mInputFocusTransferStarted = false;
 
-                        if (!mSceneContainerFlags.isEnabled()) {
+                        if (!SceneContainerFlag.isEnabled()) {
                             float velocity = (event.getY() - mInputFocusTransferStartY)
                                     / (event.getEventTime() - mInputFocusTransferStartMillis);
                             if (action == ACTION_CANCEL) {
@@ -612,7 +611,6 @@
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
             InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager,
             AssistUtils assistUtils,
-            SceneContainerFlags sceneContainerFlags,
             DumpManager dumpManager,
             Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
             BroadcastDispatcher broadcastDispatcher
@@ -624,7 +622,6 @@
         }
 
         mContext = context;
-        mSceneContainerFlags = sceneContainerFlags;
         mMainExecutor = mainExecutor;
         mShellInterface = shellInterface;
         mShadeViewControllerLazy = shadeViewControllerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index afd0746..8277c73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.Module
@@ -29,7 +28,6 @@
             EmptySceneModule::class,
             GoneSceneModule::class,
             QuickSettingsSceneModule::class,
-            SceneContainerFlagsModule::class,
             ShadeSceneModule::class,
         ],
 )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 62b0914..69f9443 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.Binds
@@ -40,7 +39,6 @@
             GoneSceneModule::class,
             LockscreenSceneModule::class,
             QuickSettingsSceneModule::class,
-            SceneContainerFlagsModule::class,
             ShadeSceneModule::class,
         ],
 )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 0665c9e..d202c24 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.Module
@@ -30,7 +29,6 @@
             EmptySceneModule::class,
             GoneSceneModule::class,
             LockscreenSceneModule::class,
-            SceneContainerFlagsModule::class,
         ],
 )
 object ShadelessSceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index c736707..1cf1c18 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -53,7 +53,6 @@
     private val headsUpManager: HeadsUpManager,
     private val powerInteractor: PowerInteractor,
     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
-    sceneContainerFlags: SceneContainerFlags,
     sceneInteractorProvider: Provider<SceneInteractor>,
 ) : CoreStartable {
 
@@ -68,7 +67,7 @@
      * false if the bouncer is visible.
      */
     val isLockscreenOrShadeVisible: StateFlow<Boolean> =
-        if (!sceneContainerFlags.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
         } else {
             sceneInteractorProvider
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 0e66c28..4774eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -44,7 +44,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -92,7 +92,6 @@
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val bouncerInteractor: BouncerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
-    private val flags: SceneContainerFlags,
     private val sysUiState: SysUiState,
     @DisplayId private val displayId: Int,
     private val sceneLogger: SceneLogger,
@@ -111,7 +110,7 @@
 ) : CoreStartable {
 
     override fun start() {
-        if (flags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             sceneLogger.logFrameworkEnabled(isEnabled = true)
             hydrateVisibility()
             automaticallySwitchScenes()
@@ -124,16 +123,18 @@
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
-                reason = flags.requirementDescription(),
+                reason = SceneContainerFlag.requirementDescription(),
             )
         }
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) =
         pw.asIndenting().run {
-            printSection("SceneContainerFlags") {
-                println("isEnabled", flags.isEnabled())
-                printSection("requirementDescription") { println(flags.requirementDescription()) }
+            printSection("SceneContainerFlag") {
+                println("isEnabled", SceneContainerFlag.isEnabled)
+                printSection("requirementDescription") {
+                    println(SceneContainerFlag.requirementDescription())
+                }
             }
         }
 
@@ -288,7 +289,8 @@
                                 Scenes.Gone to "device was unlocked in Bouncer scene"
                             } else {
                                 val prevScene = previousScene.value
-                                (prevScene ?: Scenes.Gone) to
+                                (prevScene
+                                    ?: Scenes.Gone) to
                                     "device was unlocked in Bouncer scene, from sceneKey=$prevScene"
                             }
                         isOnLockscreen ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
rename to packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index cff11a7..234eda8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -20,7 +20,6 @@
 
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.Flags.sceneContainer
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
@@ -32,8 +31,6 @@
 import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
-import dagger.Module
-import dagger.Provides
 
 /** Helper for reading or using the scene container flag state. */
 object SceneContainerFlag {
@@ -99,30 +96,12 @@
      */
     @JvmStatic
     inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
-}
-
-/**
- * Defines interface for classes that can check whether the scene container framework feature is
- * enabled.
- */
-interface SceneContainerFlags {
-
-    /** Returns `true` if the Scene Container Framework is enabled; `false` otherwise. */
-    fun isEnabled(): Boolean
 
     /** Returns a developer-readable string that describes the current requirement list. */
-    fun requirementDescription(): String
-}
-
-class SceneContainerFlagsImpl : SceneContainerFlags {
-
-    override fun isEnabled(): Boolean {
-        return SceneContainerFlag.isEnabled
-    }
-
-    override fun requirementDescription(): String {
+    @JvmStatic
+    fun requirementDescription(): String {
         return buildString {
-            SceneContainerFlag.getAllRequirements().forEach { requirement ->
+            getAllRequirements().forEach { requirement ->
                 append('\n')
                 append(if (requirement.isEnabled) "    [MET]" else "[NOT MET]")
                 append(" ${requirement.name}")
@@ -130,9 +109,3 @@
         }
     }
 }
-
-@Module
-object SceneContainerFlagsModule {
-
-    @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 67dc0cc..259a8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -4,7 +4,6 @@
 import android.util.AttributeSet
 import android.view.View
 import android.view.WindowInsets
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -31,7 +30,6 @@
         viewModel: SceneContainerViewModel,
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
-        flags: SceneContainerFlags,
         scenes: Set<Scene>,
         layoutInsetController: LayoutInsetsController,
         sceneDataSourceDelegator: SceneDataSourceDelegator,
@@ -44,7 +42,6 @@
             windowInsets = windowInsets,
             containerConfig = containerConfig,
             sharedNotificationContainer = sharedNotificationContainer,
-            flags = flags,
             scenes = scenes,
             onVisibilityChangedInternal = { isVisible ->
                 super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 809ac2e..2ef9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -39,7 +39,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -63,7 +63,6 @@
         windowInsets: StateFlow<WindowInsets?>,
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
-        flags: SceneContainerFlags,
         scenes: Set<Scene>,
         onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
         dataSourceDelegator: SceneDataSourceDelegator,
@@ -115,7 +114,7 @@
                     //  the SceneContainerView. This SharedNotificationContainer should contain NSSL
                     //  due to the NotificationStackScrollLayoutSection (legacy) or
                     //  NotificationSection (scene container) moving it there.
-                    if (flags.isEnabled()) {
+                    if (SceneContainerFlag.isEnabled) {
                         (sharedNotificationContainer.parent as? ViewGroup)?.removeView(
                             sharedNotificationContainer
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 706ac9c..b43a1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.HardwareRenderer;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
@@ -38,9 +39,11 @@
 import android.view.Display;
 import android.view.ScrollCaptureResponse;
 import android.view.View;
+import android.view.WindowInsets;
 import android.widget.ImageView;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.view.WindowCompat;
 
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
@@ -127,6 +130,10 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        // Enable edge-to-edge explicitly.
+        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+
         setContentView(R.layout.long_screenshot);
 
         mPreview = requireViewById(R.id.preview);
@@ -149,6 +156,13 @@
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
                         updateImageDimensions());
 
+        requireViewById(R.id.root).setOnApplyWindowInsetsListener(
+                (view, windowInsets) -> {
+                    Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+                    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+                    return WindowInsets.CONSUMED;
+                });
+
         Intent intent = getIntent();
         mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
         mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index d9a5102..5f835b3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -95,7 +95,7 @@
                                     // mean that the new action must be inserted here.
                                     val actionButton =
                                         layoutInflater.inflate(
-                                            R.layout.overlay_action_chip,
+                                            R.layout.shelf_action_chip,
                                             actionsContainer,
                                             false
                                         )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index fb32b9f..adcb14a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,7 +59,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -117,7 +117,6 @@
     private final AuthController mAuthController;
     private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
-    private final SceneContainerFlags mSceneContainerFlags;
     private final Lazy<CommunalInteractor> mCommunalInteractor;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
@@ -166,7 +165,6 @@
             ShadeWindowLogger logger,
             Lazy<SelectedUserInteractor> userInteractor,
             UserTracker userTracker,
-            SceneContainerFlags sceneContainerFlags,
             Lazy<CommunalInteractor> communalInteractor) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
@@ -186,7 +184,6 @@
         dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
         mAuthController = authController;
         mUserInteractor = userInteractor;
-        mSceneContainerFlags = sceneContainerFlags;
         mCommunalInteractor = communalInteractor;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
@@ -289,7 +286,7 @@
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             // This prevents the appearance and disappearance of the software keyboard (also known
             // as the "IME") from scrolling/panning the window to make room for the keyboard.
             //
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 648d4b5..a0c9391 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.qs.QSContainerController
 import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.data.repository.PrivacyChipRepository
 import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
 import com.android.systemui.shade.data.repository.ShadeRepository
@@ -50,11 +50,10 @@
         @Provides
         @SysUISingleton
         fun provideBaseShadeInteractor(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
             sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
         ): BaseShadeInteractor {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
@@ -64,11 +63,10 @@
         @Provides
         @SysUISingleton
         fun provideShadeController(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeControllerSceneImpl>,
             sceneContainerOff: Provider<ShadeControllerImpl>
         ): ShadeController {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
@@ -78,11 +76,10 @@
         @Provides
         @SysUISingleton
         fun provideShadeAnimationInteractor(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
             sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl>
         ): ShadeAnimationInteractor {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
@@ -92,11 +89,10 @@
         @Provides
         @SysUISingleton
         fun provideShadeBackActionInteractor(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
             sceneContainerOff: Provider<NotificationPanelViewController>
         ): ShadeBackActionInteractor {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
@@ -106,11 +102,10 @@
         @Provides
         @SysUISingleton
         fun provideShadeLockscreenInteractor(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeLockscreenInteractorImpl>,
             sceneContainerOff: Provider<NotificationPanelViewController>
         ): ShadeLockscreenInteractor {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
@@ -120,11 +115,10 @@
         @Provides
         @SysUISingleton
         fun providePanelExpansionInteractor(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<PanelExpansionInteractorImpl>,
             sceneContainerOff: Provider<NotificationPanelViewController>
         ): PanelExpansionInteractor {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
@@ -134,11 +128,10 @@
         @Provides
         @SysUISingleton
         fun provideQuickSettingsController(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
             sceneContainerOff: Provider<QuickSettingsControllerImpl>,
         ): QuickSettingsController {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index f5dd5e4..bc23778 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -78,15 +78,13 @@
         @SysUISingleton
         fun providesWindowRootView(
             layoutInflater: LayoutInflater,
-            sceneContainerFlags: SceneContainerFlags,
             viewModelProvider: Provider<SceneContainerViewModel>,
             containerConfigProvider: Provider<SceneContainerConfig>,
-            flagsProvider: Provider<SceneContainerFlags>,
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
             sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
         ): WindowRootView {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 checkNoSceneDuplicates(scenesProvider.get())
                 val sceneWindowRootView =
                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
@@ -95,7 +93,6 @@
                     containerConfig = containerConfigProvider.get(),
                     sharedNotificationContainer =
                         sceneWindowRootView.requireViewById(R.id.shared_notification_container),
-                    flags = flagsProvider.get(),
                     scenes = scenesProvider.get(),
                     layoutInsetController = layoutInsetController,
                     sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
@@ -115,9 +112,8 @@
         @SysUISingleton
         fun providesNotificationShadeWindowView(
             root: WindowRootView,
-            sceneContainerFlags: SceneContainerFlags,
         ): NotificationShadeWindowView {
-            if (sceneContainerFlags.isEnabled()) {
+            if (SceneContainerFlag.isEnabled) {
                 return root.requireViewById(R.id.legacy_window_root)
             }
             return root as NotificationShadeWindowView?
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 72a9c8d..6c76061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -22,9 +22,11 @@
 import android.icu.text.DateFormat
 import android.icu.text.DisplayContext
 import android.os.UserHandle
+import android.provider.Settings
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.res.R
@@ -54,6 +56,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     context: Context,
+    private val activityStarter: ActivityStarter,
     shadeInteractor: ShadeInteractor,
     mobileIconsInteractor: MobileIconsInteractor,
     val mobileIconsViewModel: MobileIconsViewModel,
@@ -136,6 +139,14 @@
         clockInteractor.launchClockActivity()
     }
 
+    /** Notifies that the shadeCarrierGroup was clicked. */
+    fun onShadeCarrierGroupClicked() {
+        activityStarter.postStartActivityDismissingKeyguard(
+            Intent(Settings.ACTION_WIRELESS_SETTINGS),
+            0
+        )
+    }
+
     private fun updateDateTexts(invalidateFormats: Boolean) {
         if (invalidateFormats) {
             longerDateFormat.value = getFormatFromPattern(longerPattern)
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 980f665a..6800c61 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
@@ -37,6 +37,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -64,6 +65,7 @@
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     private val sceneInteractor: SceneInteractor,
+    unfoldTransitionInteractor: UnfoldTransitionInteractor,
 ) {
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         combine(
@@ -106,6 +108,16 @@
 
     val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
 
+    /**
+     * The unfold transition progress. When fully-unfolded, this is `1` and fully folded, it's `0`.
+     */
+    val unfoldTransitionProgress: StateFlow<Float> =
+        unfoldTransitionInteractor.unfoldProgress.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = 1f
+        )
+
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() {
         if (!isClickable.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index e5b6497..594c191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -35,7 +35,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeSurface;
@@ -61,8 +61,6 @@
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import javax.inject.Provider;
-
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
@@ -70,6 +68,8 @@
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
+import javax.inject.Provider;
+
 /**
  * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
  * this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -185,10 +185,9 @@
     @Provides
     @SysUISingleton
     static ShadeSurface provideShadeSurface(
-            SceneContainerFlags sceneContainerFlags,
             Provider<ShadeSurfaceImpl> sceneContainerOn,
             Provider<NotificationPanelViewController> sceneContainerOff) {
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             return sceneContainerOn.get();
         } else {
             return sceneContainerOff.get();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 741102b..cf5366b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -49,7 +48,6 @@
 class SharedNotificationContainerBinder
 @Inject
 constructor(
-    private val sceneContainerFlags: SceneContainerFlags,
     private val controller: NotificationStackScrollLayoutController,
     private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
     private val notificationScrollViewBinder: NotificationScrollViewBinder,
@@ -130,7 +128,7 @@
                             .collect { controller.setMaxDisplayedNotifications(it) }
                     }
 
-                    if (!sceneContainerFlags.isEnabled()) {
+                    if (!SceneContainerFlag.isEnabled) {
                         launch {
                             viewModel.bounds.collect {
                                 val animate =
@@ -166,7 +164,7 @@
                 }
             }
 
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             disposables += notificationScrollViewBinder.bindWhileAttached()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index b284179..ca19da5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -43,7 +43,6 @@
     dumpManager: DumpManager,
     private val interactor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
-    flags: SceneContainerFlags,
     featureFlags: FeatureFlagsClassic,
     private val keyguardInteractor: KeyguardInteractor,
 ) : FlowDumperImpl(dumpManager) {
@@ -51,7 +50,7 @@
     val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
 
     /** DEBUG: whether the debug logging should be output. */
-    val isDebugLoggingEnabled: Boolean = flags.isEnabled()
+    val isDebugLoggingEnabled: Boolean = SceneContainerFlag.isEnabled
 
     /** Notifies that the bounds of the notification scrim have changed. */
     fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1d6b744..cb3c03e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,7 +164,6 @@
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -592,9 +591,6 @@
 
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
-
-    private final SceneContainerFlags mSceneContainerFlags;
-
     private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
 
     /**
@@ -708,7 +704,6 @@
             UserTracker userTracker,
             Provider<FingerprintManager> fingerprintManager,
             ActivityStarter activityStarter,
-            SceneContainerFlags sceneContainerFlags,
             BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
     ) {
         mContext = context;
@@ -804,7 +799,6 @@
         mUserTracker = userTracker;
         mFingerprintManager = fingerprintManager;
         mActivityStarter = activityStarter;
-        mSceneContainerFlags = sceneContainerFlags;
         mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -1088,7 +1082,7 @@
         mJavaAdapter.alwaysCollectFlow(
                 mCommunalInteractor.isIdleOnCommunal(),
                 mIdleOnCommunalConsumer);
-        if (mSceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             mJavaAdapter.alwaysCollectFlow(
                     mBrightnessMirrorShowingInteractor.isShowing(),
                     this::setBrightnessMirrorShowing
@@ -1277,7 +1271,7 @@
 
         // Set up the quick settings tile panel
         final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
-        if (container != null && !mSceneContainerFlags.isEnabled()) {
+        if (container != null && !SceneContainerFlag.isEnabled()) {
             FragmentHostManager fragmentHostManager =
                     mFragmentService.getFragmentHostManager(container);
             ExtensionFragmentListener.attachExtensonToFragment(
@@ -1545,8 +1539,7 @@
                 mShadeSurface,
                 mShadeExpansionStateManager,
                 mBiometricUnlockController,
-                mStackScroller,
-                mKeyguardBypassController);
+                mStackScroller);
         mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a3d316b..a8941bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.phone
 
 import android.annotation.IntDef
-import android.content.Context
 import android.content.pm.PackageManager
+import android.content.res.Resources
 import android.hardware.biometrics.BiometricSourceType
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
@@ -26,7 +26,10 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.ShadeRepository
@@ -46,12 +49,20 @@
 import javax.inject.Inject
 
 @SysUISingleton
-open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassController {
+class KeyguardBypassController @Inject constructor(
+        @Main resources: Resources,
+        packageManager: PackageManager,
+        @Application applicationScope: CoroutineScope,
+        tunerService: TunerService,
+        private val statusBarStateController: StatusBarStateController,
+        lockscreenUserManager: NotificationLockscreenUserManager,
+        private val keyguardStateController: KeyguardStateController,
+        private val shadeRepository: ShadeRepository,
+        devicePostureController: DevicePostureController,
+        private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+        dumpManager: DumpManager
+) : Dumpable, StackScrollAlgorithm.BypassController {
 
-    private val mKeyguardStateController: KeyguardStateController
-    private val statusBarStateController: StatusBarStateController
-    private val shadeRepository: ShadeRepository
-    private val devicePostureController: DevicePostureController
     @BypassOverride private val bypassOverride: Int
     private var hasFaceFeature: Boolean
     @DevicePostureInt private val configFaceAuthSupportedPosture: Int
@@ -99,7 +110,7 @@
                 FACE_UNLOCK_BYPASS_NEVER -> false
                 else -> field
             }
-            return enabled && mKeyguardStateController.isFaceEnrolledAndEnabled &&
+            return enabled && keyguardStateController.isFaceEnrolledAndEnabled &&
                     isPostureAllowedForFaceAuth()
         }
         private set(value) {
@@ -108,70 +119,44 @@
         }
 
     var bouncerShowing: Boolean = false
-    var altBouncerShowing: Boolean = false
     var launchingAffordance: Boolean = false
     var qsExpanded = false
 
-    @Inject
-    constructor(
-        context: Context,
-        @Application applicationScope: CoroutineScope,
-        tunerService: TunerService,
-        statusBarStateController: StatusBarStateController,
-        lockscreenUserManager: NotificationLockscreenUserManager,
-        keyguardStateController: KeyguardStateController,
-        shadeRepository: ShadeRepository,
-        devicePostureController: DevicePostureController,
-        dumpManager: DumpManager
-    ) {
-        this.mKeyguardStateController = keyguardStateController
-        this.statusBarStateController = statusBarStateController
-        this.shadeRepository = shadeRepository
-        this.devicePostureController = devicePostureController
-
-        bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+    init {
+        bypassOverride = resources.getInteger(R.integer.config_face_unlock_bypass_override)
         configFaceAuthSupportedPosture =
-            context.resources.getInteger(R.integer.config_face_auth_supported_posture)
-
-        hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
-        if (!hasFaceFeature) {
-            return
-        }
-
-
-        if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
-            devicePostureController.addCallback { posture ->
-                if (postureState != posture) {
-                    postureState = posture
-                    notifyListeners()
+            resources.getInteger(R.integer.config_face_auth_supported_posture)
+        hasFaceFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
+        if (hasFaceFeature) {
+            if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
+                devicePostureController.addCallback { posture ->
+                    if (postureState != posture) {
+                        postureState = posture
+                        notifyListeners()
+                    }
                 }
             }
-        }
-
-        dumpManager.registerNormalDumpable("KeyguardBypassController", this)
-        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onStateChanged(newState: Int) {
-                if (newState != StatusBarState.KEYGUARD) {
-                    pendingUnlock = null
-                }
-            }
-        })
-
-        listenForQsExpandedChange(applicationScope)
-
-        val dismissByDefault = if (context.resources.getBoolean(
-                com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
-
-        tunerService.addTunable({ key, _ ->
-            bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
-        }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
-
-        lockscreenUserManager.addUserChangedListener(
-            object : NotificationLockscreenUserManager.UserChangedListener {
-                override fun onUserChanged(userId: Int) {
-                    pendingUnlock = null
+            dumpManager.registerNormalDumpable("KeyguardBypassController", this)
+            statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+                override fun onStateChanged(newState: Int) {
+                    if (newState != StatusBarState.KEYGUARD) {
+                        pendingUnlock = null
+                    }
                 }
             })
+            listenForQsExpandedChange(applicationScope)
+            val dismissByDefault = if (resources.getBoolean(
+                            com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
+            tunerService.addTunable({ key, _ ->
+                bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
+            }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
+            lockscreenUserManager.addUserChangedListener(
+                    object : NotificationLockscreenUserManager.UserChangedListener {
+                        override fun onUserChanged(userId: Int) {
+                            pendingUnlock = null
+                        }
+                    })
+        }
     }
 
     @VisibleForTesting
@@ -228,7 +213,8 @@
         if (bypassEnabled) {
             return when {
                 bouncerShowing -> true
-                altBouncerShowing -> true
+                keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER ->
+                    true
                 statusBarStateController.state != StatusBarState.KEYGUARD -> false
                 launchingAffordance -> false
                 isPulseExpanding || qsExpanded -> false
@@ -260,7 +246,8 @@
         pw.println("  bypassEnabled: $bypassEnabled")
         pw.println("  canBypass: ${canBypass()}")
         pw.println("  bouncerShowing: $bouncerShowing")
-        pw.println("  altBouncerShowing: $altBouncerShowing")
+        pw.println("  altBouncerShowing:" +
+            " ${keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER}")
         pw.println("  isPulseExpanding: $isPulseExpanding")
         pw.println("  launchingAffordance: $launchingAffordance")
         pw.println("  qSExpanded: $qsExpanded")
@@ -273,7 +260,7 @@
         val start = listeners.isEmpty()
         listeners.add(listener)
         if (start) {
-            mKeyguardStateController.addCallback(faceAuthEnabledChangedCallback)
+            keyguardStateController.addCallback(faceAuthEnabledChangedCallback)
         }
     }
 
@@ -284,7 +271,7 @@
     fun unregisterOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
         listeners.remove(listener)
         if (listeners.isEmpty()) {
-            mKeyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
+            keyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 92fd90a..5206e46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -24,10 +24,10 @@
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeLogger
@@ -65,7 +65,6 @@
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
     private val userChipViewModel: StatusBarUserChipViewModel,
     private val viewUtil: ViewUtil,
-    private val sceneContainerFlags: SceneContainerFlags,
     private val configurationController: ConfigurationController,
     private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
 ) : ViewController<PhoneStatusBarView>(view) {
@@ -205,7 +204,7 @@
 
             // If scene framework is enabled, route the touch to it and
             // ignore the rest of the gesture.
-            if (sceneContainerFlags.isEnabled()) {
+            if (SceneContainerFlag.isEnabled) {
                 windowRootView.get().dispatchTouchEvent(event)
                 return true
             }
@@ -267,7 +266,6 @@
         @Named(UNFOLD_STATUS_BAR)
         private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
         private val featureFlags: FeatureFlags,
-        private val sceneContainerFlags: SceneContainerFlags,
         private val userChipViewModel: StatusBarUserChipViewModel,
         private val centralSurfaces: CentralSurfaces,
         private val statusBarWindowStateController: StatusBarWindowStateController,
@@ -301,7 +299,6 @@
                 statusBarMoveFromCenterAnimationController,
                 userChipViewModel,
                 viewUtil,
-                sceneContainerFlags,
                 configurationController,
                 statusOverlayHoverListenerFactory,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index dd7d282..a141b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -332,7 +332,6 @@
     private final LatencyTracker mLatencyTracker;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
     private final SelectedUserInteractor mSelectedUserInteractor;
-    @Nullable private KeyguardBypassController mBypassController;
     @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
 
     @Nullable private TaskbarDelegate mTaskbarDelegate;
@@ -440,8 +439,7 @@
             ShadeLockscreenInteractor shadeLockscreenInteractor,
             ShadeExpansionStateManager shadeExpansionStateManager,
             BiometricUnlockController biometricUnlockController,
-            View notificationContainer,
-            KeyguardBypassController bypassController) {
+            View notificationContainer) {
         mCentralSurfaces = centralSurfaces;
         mBiometricUnlockController = biometricUnlockController;
 
@@ -452,7 +450,6 @@
                     shadeExpansionStateManager.addExpansionListener(this);
             onPanelExpansionChanged(currentState);
         }
-        mBypassController = bypassController;
         mNotificationContainer = notificationContainer;
         if (!DeviceEntryUdfpsRefactor.isEnabled()) {
             mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
@@ -973,7 +970,6 @@
             mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
             mKeyguardMessageAreaController.setMessage("");
         }
-        mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
         mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
 
         if (updateScrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 8e8de46..d1189e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,7 +37,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -91,7 +91,6 @@
             ShadeInteractor shadeInteractor,
             Provider<SceneInteractor> sceneInteractor,
             JavaAdapter javaAdapter,
-            SceneContainerFlags sceneContainerFlags,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor
@@ -130,7 +129,7 @@
 
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
 
-        if (sceneContainerFlags.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             javaAdapter.alwaysCollectFlow(
                     sceneInteractor.get().isVisible(),
                     this::onSceneContainerVisibilityChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 541ac48..31c2134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -15,36 +15,55 @@
  */
 package com.android.systemui.statusbar.phone
 
+import android.annotation.StyleRes
 import android.app.Dialog
 import android.content.Context
-import android.content.res.Configuration
 import android.graphics.Color
 import android.graphics.drawable.ColorDrawable
 import android.os.Bundle
 import android.view.Gravity
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.WindowInsets
-import android.view.WindowInsets.Type.InsetsType
-import android.view.WindowInsetsAnimation
 import android.view.WindowManager.LayoutParams.MATCH_PARENT
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
 import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import androidx.activity.ComponentDialog
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.onConfigChanged
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
 
 /** A dialog shown as a bottom sheet. */
-open class SystemUIBottomSheetDialog(
+class SystemUIBottomSheetDialog
+@VisibleForTesting
+constructor(
     context: Context,
-    private val configurationController: ConfigurationController? = null,
-    theme: Int = R.style.Theme_SystemUI_Dialog
-) : Dialog(context, theme) {
+    private val coroutineScope: CoroutineScope,
+    private val configurationController: ConfigurationController,
+    private val delegate: DialogDelegate<in Dialog>,
+    private val windowLayout: WindowLayout,
+    theme: Int,
+) : ComponentDialog(context, theme) {
+
+    private var job: Job? = null
+
     override fun onCreate(savedInstanceState: Bundle?) {
+        delegate.beforeCreate(this, savedInstanceState)
         super.onCreate(savedInstanceState)
         setupWindow()
-        setupEdgeToEdge()
         setCanceledOnTouchOutside(true)
+        delegate.onCreate(this, savedInstanceState)
     }
 
     private fun setupWindow() {
@@ -62,60 +81,84 @@
         }
     }
 
-    private fun setupEdgeToEdge() {
-        val edgeToEdgeHorizontally =
-            context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
-        val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
-        val height = WRAP_CONTENT
-        window?.setLayout(width, height)
-    }
-
     override fun onStart() {
         super.onStart()
-        configurationController?.addCallback(onConfigChanged)
-        window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
+        job?.cancel()
+        job =
+            coroutineScope.launch {
+                windowLayout
+                    .calculate()
+                    .onEach { window?.apply { setLayout(it.width, it.height) } }
+                    .launchIn(this)
+                configurationController.onConfigChanged
+                    .onEach { delegate.onConfigurationChanged(this@SystemUIBottomSheetDialog, it) }
+                    .launchIn(this)
+            }
+        delegate.onStart(this)
     }
 
     override fun onStop() {
+        job?.cancel()
+        delegate.onStop(this)
         super.onStop()
-        configurationController?.removeCallback(onConfigChanged)
-        window?.decorView?.setWindowInsetsAnimationCallback(null)
     }
 
-    /** Called after any insets change. */
-    open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+    override fun onWindowFocusChanged(hasFocus: Boolean) {
+        super.onWindowFocusChanged(hasFocus)
+        delegate.onWindowFocusChanged(this, hasFocus)
+    }
 
-    /** Can be overridden by subclasses to receive config changed events. */
-    open fun onConfigurationChanged() {}
+    class Factory
+    @Inject
+    constructor(
+        @Application private val context: Context,
+        @Application private val coroutineScope: CoroutineScope,
+        private val defaultWindowLayout: Lazy<WindowLayout.LimitedEdgeToEdge>,
+        private val configurationController: ConfigurationController,
+    ) {
 
-    private val insetsAnimationCallback =
-        object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+        fun create(
+            delegate: DialogDelegate<in Dialog>,
+            windowLayout: WindowLayout = defaultWindowLayout.get(),
+            @StyleRes theme: Int = R.style.Theme_SystemUI_Dialog,
+        ): SystemUIBottomSheetDialog =
+            SystemUIBottomSheetDialog(
+                context = context,
+                configurationController = configurationController,
+                coroutineScope = coroutineScope,
+                delegate = delegate,
+                windowLayout = windowLayout,
+                theme = theme,
+            )
+    }
 
-            private var lastInsets: WindowInsets? = null
+    /** [SystemUIBottomSheetDialog] uses this to determine the [android.view.Window] layout. */
+    interface WindowLayout {
 
-            override fun onEnd(animation: WindowInsetsAnimation) {
-                lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
-            }
+        /** Returns a [Layout] to apply to [android.view.Window.setLayout]. */
+        fun calculate(): Flow<Layout>
 
-            override fun onProgress(
-                insets: WindowInsets,
-                animations: MutableList<WindowInsetsAnimation>,
-            ): WindowInsets {
-                lastInsets = insets
-                onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
-                return insets
-            }
+        /** Edge to edge with which doesn't fill the whole space on the large screen. */
+        class LimitedEdgeToEdge
+        @Inject
+        constructor(
+            @Application private val context: Context,
+            private val configurationController: ConfigurationController,
+        ) : WindowLayout {
 
-            private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
-                animations.fold(0) { acc: Int, it -> acc or it.typeMask }
-        }
+            override fun calculate(): Flow<Layout> {
+                return configurationController.onConfigChanged
+                    .onStart { emit(context.resources.configuration) }
+                    .map {
+                        val edgeToEdgeHorizontally =
+                            context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+                        val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
 
-    private val onConfigChanged =
-        object : ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                super.onConfigChanged(newConfig)
-                setupEdgeToEdge()
-                onConfigurationChanged()
+                        Layout(width, WRAP_CONTENT)
+                    }
             }
         }
+
+        data class Layout(val width: Int, val height: Int)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index 3b2930f..f4e3eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,7 +18,6 @@
 
 import android.os.PersistableBundle
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT
 import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
 import androidx.annotation.VisibleForTesting
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,11 +42,10 @@
  * using the default config for logging purposes.
  *
  * NOTE to add new keys to be tracked:
- * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig]
- * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or
- *    [IntCarrierConfig.config]
- * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated
- *    when a new carrier config comes down
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
+ * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
+ *    updated when a new carrier config comes down
  */
 class SystemUiCarrierConfig
 internal constructor(
@@ -68,16 +66,10 @@
     /** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
     val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
 
-    private val satelliteHysteresisSeconds =
-        IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig)
-    /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */
-    val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config
-
     private val trackedConfigs =
         listOf(
             inflateSignalStrength,
             showOperatorName,
-            satelliteHysteresisSeconds,
         )
 
     /** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
@@ -98,19 +90,15 @@
     override fun toString(): String = trackedConfigs.joinToString { it.toString() }
 }
 
-interface CarrierConfig {
-    fun update(config: PersistableBundle)
-}
-
 /** Extracts [key] from the carrier config, and stores it in a flow */
 private class BooleanCarrierConfig(
     val key: String,
     defaultConfig: PersistableBundle,
-) : CarrierConfig {
+) {
     private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
     val config = _configValue.asStateFlow()
 
-    override fun update(config: PersistableBundle) {
+    fun update(config: PersistableBundle) {
         _configValue.value = config.getBoolean(key)
     }
 
@@ -118,20 +106,3 @@
         return "$key=${config.value}"
     }
 }
-
-/** Extracts [key] from the carrier config, and stores it in a flow */
-private class IntCarrierConfig(
-    val key: String,
-    defaultConfig: PersistableBundle,
-) : CarrierConfig {
-    private val _configValue = MutableStateFlow(defaultConfig.getInt(key))
-    val config = _configValue.asStateFlow()
-
-    override fun update(config: PersistableBundle) {
-        _configValue.value = config.getInt(key)
-    }
-
-    override fun toString(): String {
-        return "$key=${config.value}"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 317c063..2278597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -144,9 +144,6 @@
      */
     val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
 
-    /** Duration in seconds of the hysteresis to use when losing satellite connection. */
-    val satelliteConnectionHysteresisSeconds: StateFlow<Int>
-
     /**
      * True if this connection is in emergency callback mode.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 90cdfeb..83d5f2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -227,8 +227,6 @@
 
     override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
 
-    override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
     override suspend fun isInEcmMode(): Boolean = false
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index cb99d11..a532e62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -187,9 +187,6 @@
      */
     override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
 
-    /** Non-applicable to carrier merged connections. */
-    override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow()
-
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     override suspend fun isInEcmMode(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index bb0af77..41559b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.util.IndentingPrintWriter
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.table.TableLogBuffer
@@ -24,6 +25,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -349,17 +351,29 @@
                 activeRepo.value.hasPrioritizedNetworkCapabilities.value,
             )
 
-    override val satelliteConnectionHysteresisSeconds =
-        activeRepo
-            .flatMapLatest { it.satelliteConnectionHysteresisSeconds }
-            .stateIn(
-                scope,
-                SharingStarted.WhileSubscribed(),
-                activeRepo.value.satelliteConnectionHysteresisSeconds.value
-            )
-
     override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode()
 
+    fun dump(pw: PrintWriter) {
+        val ipw = IndentingPrintWriter(pw, "  ")
+
+        ipw.println("MobileConnectionRepository[$subId]")
+        ipw.increaseIndent()
+
+        ipw.println("carrierMerged=${_isCarrierMerged.value}")
+
+        ipw.print("Type (cellular or carrier merged): ")
+        when (activeRepo.value) {
+            is CarrierMergedConnectionRepository -> ipw.println("Carrier merged")
+            is MobileConnectionRepositoryImpl -> ipw.println("Cellular")
+        }
+
+        ipw.increaseIndent()
+        ipw.println("Provider: ${activeRepo.value}")
+        ipw.decreaseIndent()
+
+        ipw.decreaseIndent()
+    }
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 2cbe965..b3885d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -450,9 +450,6 @@
             .flowOn(bgDispatcher)
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
-    override val satelliteConnectionHysteresisSeconds: StateFlow<Int> =
-        systemUiCarrierConfig.satelliteConnectionHysteresisSeconds
-
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index a455db2..5d91ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -26,17 +26,20 @@
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
+import android.util.IndentingPrintWriter
 import androidx.annotation.VisibleForTesting
 import com.android.internal.telephony.PhoneConstants
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.res.R
@@ -52,6 +55,8 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -97,8 +102,12 @@
     wifiRepository: WifiRepository,
     private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-) : MobileConnectionsRepository {
-    private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
+    private val dumpManager: DumpManager,
+) : MobileConnectionsRepository, Dumpable {
+
+    // TODO(b/333912012): for now, we are never invalidating the cache. We can do better though
+    private var subIdRepositoryCache:
+        MutableMap<Int, WeakReference<FullMobileConnectionRepository>> =
         mutableMapOf()
 
     private val defaultNetworkName =
@@ -109,6 +118,10 @@
     private val networkNameSeparator: String =
         context.getString(R.string.status_bar_network_name_separator)
 
+    init {
+        dumpManager.registerNormalDumpable("MobileConnectionsRepository", this)
+    }
+
     private val carrierMergedSubId: StateFlow<Int?> =
         combine(
                 wifiRepository.wifiNetwork,
@@ -283,8 +296,10 @@
         getOrCreateRepoForSubId(subId)
 
     private fun getOrCreateRepoForSubId(subId: Int) =
-        subIdRepositoryCache[subId]
-            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+        subIdRepositoryCache[subId]?.get()
+            ?: createRepositoryForSubId(subId).also {
+                subIdRepositoryCache[subId] = WeakReference(it)
+            }
 
     override val mobileIsDefault: StateFlow<Boolean> =
         connectivityRepository.defaultConnections
@@ -374,9 +389,8 @@
     }
 
     private fun updateRepos(newInfos: List<SubscriptionModel>) {
-        dropUnusedReposFromCache(newInfos)
         subIdRepositoryCache.forEach { (subId, repo) ->
-            repo.setIsCarrierMerged(isCarrierMerged(subId))
+            repo.get()?.setIsCarrierMerged(isCarrierMerged(subId))
         }
     }
 
@@ -384,13 +398,6 @@
         return subId == carrierMergedSubId.value
     }
 
-    private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
-        // Remove any connection repository from the cache that isn't in the new set of IDs. They
-        // will get garbage collected once their subscribers go away
-        subIdRepositoryCache =
-            subIdRepositoryCache.filter { checkSub(it.key, newInfos) }.toMutableMap()
-    }
-
     /**
      * True if the checked subId is in the list of current subs or the active mobile data subId
      *
@@ -422,6 +429,22 @@
             profileClass = profileClass,
         )
 
+    override fun dump(pw: PrintWriter, args: Array<String>) {
+        val ipw = IndentingPrintWriter(pw, " ")
+        ipw.println("Connection cache:")
+
+        ipw.increaseIndent()
+        subIdRepositoryCache.entries.forEach { (subId, repo) ->
+            ipw.println("$subId: ${repo.get()}")
+        }
+        ipw.decreaseIndent()
+
+        ipw.println("Connections (${subIdRepositoryCache.size} total):")
+        ipw.increaseIndent()
+        subIdRepositoryCache.values.forEach { it.get()?.dump(ipw) }
+        ipw.decreaseIndent()
+    }
+
     companion object {
         private const val LOGGING_PREFIX = "Repo"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index cbebfd0..ed9e405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -35,25 +35,18 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.util.kotlin.pairwiseBy
-import kotlin.time.DurationUnit
-import kotlin.time.toDuration
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 interface MobileIconInteractor {
     /** The table log created for this connection */
@@ -269,43 +262,6 @@
             MutableStateFlow(false).asStateFlow()
         }
 
-    private val hysteresisActive = MutableStateFlow(false)
-
-    private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> =
-        combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive ->
-                if (hysteresisActive) {
-                    true
-                } else {
-                    isNonTerrestrial
-                }
-            }
-            .logDiffsForTable(
-                tableLogBuffer = tableLogBuffer,
-                columnName = "isNonTerrestrialWithHysteresis",
-                columnPrefix = "",
-                initialValue = Flags.carrierEnabledSatelliteFlag(),
-            )
-            .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag())
-
-    private val lostSatelliteConnection =
-        isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new }
-
-    init {
-        scope.launch { lostSatelliteConnection.collect() }
-        scope.launch {
-            hysteresisActive.collectLatest {
-                if (it) {
-                    delay(
-                        connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration(
-                            DurationUnit.SECONDS
-                        )
-                    )
-                    hysteresisActive.value = false
-                }
-            }
-        }
-    }
-
     override val isRoaming: StateFlow<Boolean> =
         combine(
                 connectionRepository.carrierNetworkChangeActive,
@@ -407,7 +363,7 @@
                 showExclamationMark.value,
                 carrierNetworkChangeActive.value,
             )
-        isNonTerrestrialWithHysteresis
+        isNonTerrestrial
             .flatMapLatest { ntn ->
                 if (ntn) {
                     satelliteIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a0c5618..5f08afd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -128,6 +128,8 @@
                                 mobileGroupView,
                                 dotView,
                             )
+
+                            view.requestLayout()
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 08ed030..054116d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -41,6 +43,7 @@
     val repo: DeviceBasedSatelliteRepository,
     iconsInteractor: MobileIconsInteractor,
     deviceProvisioningInteractor: DeviceProvisioningInteractor,
+    wifiInteractor: WifiInteractor,
     @Application scope: CoroutineScope,
 ) {
     /** Must be observed by any UI showing Satellite iconography */
@@ -73,6 +76,9 @@
 
     val isDeviceProvisioned: Flow<Boolean> = deviceProvisioningInteractor.isDeviceProvisioned
 
+    val isWifiActive: Flow<Boolean> =
+        wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
+
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 40641be..a0291b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -59,9 +59,10 @@
                 combine(
                     interactor.isSatelliteAllowed,
                     interactor.isDeviceProvisioned,
+                    interactor.isWifiActive,
                     airplaneModeRepository.isAirplaneMode
-                ) { isSatelliteAllowed, isDeviceProvisioned, isAirplaneMode ->
-                    isSatelliteAllowed && isDeviceProvisioned && !isAirplaneMode
+                ) { isSatelliteAllowed, isDeviceProvisioned, isWifiActive, isAirplaneMode ->
+                    isSatelliteAllowed && isDeviceProvisioned && !isWifiActive && !isAirplaneMode
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
index 0d3682c..fbbd2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
@@ -15,9 +15,11 @@
  */
 package com.android.systemui.unfold.data.repository
 
+import androidx.annotation.FloatRange
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
@@ -42,6 +44,10 @@
 sealed class UnfoldTransitionStatus {
     /** Status that is sent when fold or unfold transition is in started state */
     data object TransitionStarted : UnfoldTransitionStatus()
+    /** Status that is sent while fold or unfold transition is in progress */
+    data class TransitionInProgress(
+        @FloatRange(from = 0.0, to = 1.0) val progress: Float,
+    ) : UnfoldTransitionStatus()
     /** Status that is sent when fold or unfold transition is finished */
     data object TransitionFinished : UnfoldTransitionStatus()
 }
@@ -66,6 +72,10 @@
                             trySend(TransitionStarted)
                         }
 
+                        override fun onTransitionProgress(progress: Float) {
+                            trySend(TransitionInProgress(progress))
+                        }
+
                         override fun onTransitionFinished() {
                             trySend(TransitionFinished)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index 3e2e564..a8e4496 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -17,10 +17,14 @@
 
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
 
 /**
  * Contains business-logic related to fold-unfold transitions while interacting with
@@ -30,6 +34,8 @@
     /** Returns availability of fold/unfold transitions on the device */
     val isAvailable: Boolean
 
+    val unfoldProgress: Flow<Float>
+
     /** Suspends and waits for a fold/unfold transition to finish */
     suspend fun waitForTransitionFinish()
 
@@ -44,6 +50,11 @@
     override val isAvailable: Boolean
         get() = repository.isAvailable
 
+    override val unfoldProgress: Flow<Float> =
+        repository.transitionStatus
+            .map { (it as? TransitionInProgress)?.progress ?: 1f }
+            .distinctUntilChanged()
+
     override suspend fun waitForTransitionFinish() {
         repository.transitionStatus.filter { it is TransitionFinished }.first()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
new file mode 100644
index 0000000..ee00e8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.ReduceBrightColorsController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun ReduceBrightColorsController.isEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val callback =
+                object : ReduceBrightColorsController.Listener {
+                    override fun onActivated(activated: Boolean) {
+                        trySend(activated)
+                    }
+                }
+            addCallback(callback)
+            awaitClose { removeCallback(callback) }
+        }
+        .onStart { emit(isReduceBrightColorsActivated) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 7edb5a5..df19013 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -17,6 +17,7 @@
 package com.android.systemui.volume;
 
 import android.media.AudioManager;
+import android.util.MathUtils;
 import android.view.View;
 
 /**
@@ -46,4 +47,27 @@
         if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
         v.setVisibility(vis ? View.VISIBLE : View.GONE);
     }
+
+    /**
+     * Translates a value from one range to another.
+     *
+     * ```
+     * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
+     * Result: 37.5
+     * ```
+     */
+    public static float translateToRange(float value,
+            float valueRangeStart,
+            float valueRangeEnd,
+            float targetRangeStart,
+            float targetRangeEnd) {
+        float currentRangeLength = valueRangeEnd - valueRangeStart;
+        float targetRangeLength = targetRangeEnd - targetRangeStart;
+        if (currentRangeLength == 0f || targetRangeLength == 0f) {
+            return targetRangeStart;
+        }
+        float valueFraction = (value - valueRangeStart) / currentRangeLength;
+        return MathUtils.constrain(targetRangeStart + valueFraction * targetRangeLength,
+                targetRangeStart, targetRangeEnd);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1688b0b..2245541 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -137,13 +137,13 @@
 import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
+
 /**
  * Visual presentation of the volume dialog.
  *
@@ -166,6 +166,7 @@
 
     private static final int DRAWER_ANIMATION_DURATION_SHORT = 175;
     private static final int DRAWER_ANIMATION_DURATION = 250;
+    private static final int DISPLAY_RANGE_MULTIPLIER = 100;
 
     /** Shows volume dialog show animation. */
     private static final String TYPE_SHOW = "show";
@@ -826,12 +827,14 @@
         writer.print("  mSilentMode: "); writer.println(mSilentMode);
     }
 
-    private static int getImpliedLevel(SeekBar seekBar, int progress) {
-        final int m = seekBar.getMax();
-        final int n = m / 100 - 1;
-        final int level = progress == 0 ? 0
-                : progress == m ? (m / 100) : (1 + (int) ((progress / (float) m) * n));
-        return level;
+    private static int getVolumeFromProgress(StreamState state, SeekBar seekBar, int progress) {
+        return (int) Util.translateToRange(progress, seekBar.getMin(), seekBar.getMax(),
+                state.levelMin, state.levelMax);
+    }
+
+    private static int getProgressFromVolume(StreamState state, SeekBar seekBar, int volume) {
+        return (int) Util.translateToRange(volume, state.levelMin, state.levelMax, seekBar.getMin(),
+                seekBar.getMax());
     }
 
     @SuppressLint("InflateParams")
@@ -854,6 +857,8 @@
         addSliderHapticsToRow(row);
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
         row.number = row.view.findViewById(R.id.volume_number);
+        row.slider.setAccessibilityDelegate(
+                new VolumeDialogSeekBarAccessibilityDelegate(DISPLAY_RANGE_MULTIPLIER));
 
         row.anim = null;
 
@@ -1916,12 +1921,12 @@
                 : false;
 
         // update slider max
-        final int max = ss.levelMax * 100;
+        final int max = ss.levelMax * DISPLAY_RANGE_MULTIPLIER;
         if (max != row.slider.getMax()) {
             row.slider.setMax(max);
         }
         // update slider min
-        final int min = ss.levelMin * 100;
+        final int min = ss.levelMin * DISPLAY_RANGE_MULTIPLIER;
         if (min != row.slider.getMin()) {
             row.slider.setMin(min);
         }
@@ -2069,7 +2074,7 @@
             return;  // don't update if user is sliding
         }
         final int progress = row.slider.getProgress();
-        final int level = getImpliedLevel(row.slider, progress);
+        final int level = getVolumeFromProgress(row.ss, row.slider, progress);
         final boolean rowVisible = row.view.getVisibility() == VISIBLE;
         final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
                 < USER_ATTEMPT_GRACE_PERIOD;
@@ -2085,7 +2090,7 @@
                 return;  // don't clamp if visible
             }
         }
-        final int newProgress = vlevel * 100;
+        final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
         if (progress != newProgress) {
             if (mShowing && rowVisible) {
                 // animate!
@@ -2530,13 +2535,13 @@
                     + " onProgressChanged " + progress + " fromUser=" + fromUser);
             if (!fromUser) return;
             if (mRow.ss.levelMin > 0) {
-                final int minProgress = mRow.ss.levelMin * 100;
+                final int minProgress = getProgressFromVolume(mRow.ss, seekBar, mRow.ss.levelMin);
                 if (progress < minProgress) {
                     seekBar.setProgress(minProgress);
                     progress = minProgress;
                 }
             }
-            final int userLevel = getImpliedLevel(seekBar, progress);
+            final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, progress);
             if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
                 mRow.userAttempt = SystemClock.uptimeMillis();
                 if (mRow.requestedLevel != userLevel) {
@@ -2569,7 +2574,7 @@
             }
             mRow.tracking = false;
             mRow.userAttempt = SystemClock.uptimeMillis();
-            final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+            final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, seekBar.getProgress());
             Events.writeEvent(Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
             if (mRow.ss.level != userLevel) {
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
new file mode 100644
index 0000000..cd31a95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.volume
+
+import android.os.Bundle
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.SeekBar
+import com.android.internal.R
+
+class VolumeDialogSeekBarAccessibilityDelegate(
+    private val accessibilityStep: Int,
+) : AccessibilityDelegate() {
+
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+        require(host is SeekBar) { "This class only works with the SeekBar" }
+        val seekBar: SeekBar = host
+        if (
+            action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD ||
+                action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+        ) {
+            var increment = accessibilityStep
+            if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+                increment = -increment
+            }
+
+            return super.performAccessibilityAction(
+                host,
+                R.id.accessibilityActionSetProgress,
+                Bundle().apply {
+                    putFloat(
+                        AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE,
+                        (seekBar.progress + increment).coerceIn(seekBar.min, seekBar.max).toFloat(),
+                    )
+                },
+            )
+        }
+        return super.performAccessibilityAction(host, action, args)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
index 9f9275b..e5c5a65 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -16,13 +16,10 @@
 
 package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
 
-import com.android.systemui.common.shared.model.Color
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 
 data class SpatialAudioButtonViewModel(
     val model: SpatialAudioEnabledModel,
     val button: ToggleButtonViewModel,
-    val iconColor: Color,
-    val labelColor: Color,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4ecdd46..b5e9ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.common.shared.model.Color
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
@@ -79,26 +78,7 @@
                         val isChecked = isEnabled == currentIsEnabled
                         val buttonViewModel: ToggleButtonViewModel =
                             isEnabled.toViewModel(isChecked)
-                        SpatialAudioButtonViewModel(
-                            button = buttonViewModel,
-                            model = isEnabled,
-                            iconColor =
-                                Color.Attribute(
-                                    if (isChecked) {
-                                        com.android.internal.R.attr.materialColorOnPrimaryContainer
-                                    } else {
-                                        com.android.internal.R.attr.materialColorOnSurfaceVariant
-                                    }
-                                ),
-                            labelColor =
-                                Color.Attribute(
-                                    if (isChecked) {
-                                        com.android.internal.R.attr.materialColorOnSurface
-                                    } else {
-                                        com.android.internal.R.attr.materialColorOnSurfaceVariant
-                                    }
-                                ),
-                        )
+                        SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
                     }
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index a249961..319b615 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -205,9 +205,9 @@
         when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
         when(mClockEventController.getClock()).thenReturn(mClockController);
         when(mSmallClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
         when(mLargeClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9d81b96..99b5a4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -272,9 +272,9 @@
         assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
 
         when(mSmallClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false));
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
         when(mLargeClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false));
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
         verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
         listenerArgumentCaptor.getValue().onCurrentClockChanged();
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 11fe862..b2828a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -98,7 +98,7 @@
     public void updatePosition_primaryClockAnimation() {
         ClockController mockClock = mock(ClockController.class);
         when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
-        when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true));
+        when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true, false));
 
         mController.updatePosition(10, 15, 20f, true);
 
@@ -113,7 +113,7 @@
     public void updatePosition_alternateClockAnimation() {
         ClockController mockClock = mock(ClockController.class);
         when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
-        when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true));
+        when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true, false));
 
         mController.updatePosition(10, 15, 20f, true);
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5af0c1f..fde45d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -52,7 +52,6 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -158,6 +157,7 @@
 import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 import org.mockito.internal.util.reflection.FieldSetter;
@@ -809,31 +809,6 @@
     }
 
     @Test
-    public void whenFaceAuthenticated_biometricAuthenticatedCallback_beforeUpdatingFpState() {
-        // GIVEN listening for UDFPS fingerprint
-        when(mAuthController.isUdfpsSupported()).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        keyguardIsVisible();
-        final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
-        mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
-
-        // WHEN face is authenticated
-        when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
-        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
-        when(mFaceAuthInteractor.isLockedOut()).thenReturn(false);
-        mKeyguardUpdateMonitor.onFaceAuthenticated(0, true);
-        mTestableLooper.processAllMessages();
-
-        // THEN verify keyguardUpdateMonitorCallback receives an onAuthenticated callback
-        // before cancelling the fingerprint request
-        InOrder inOrder = inOrder(mTestCallback, fpCancel);
-        inOrder.verify(mTestCallback).onBiometricAuthenticated(
-                eq(0), eq(BiometricSourceType.FACE), eq(true));
-        inOrder.verify(fpCancel).cancel();
-    }
-
-    @Test
     public void whenDetectFingerprint_biometricDetectCallback() {
         ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
                 ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
@@ -2158,7 +2133,7 @@
                 null /* trustGrantedMessages */);
 
         // THEN onTrustChanged is called FIRST
-        final InOrder inOrder = inOrder(callback);
+        final InOrder inOrder = Mockito.inOrder(callback);
         inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
 
         // AND THEN onTrustGrantedForCurrentUser callback called
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
index f924ab4..bcea411 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
@@ -36,6 +36,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageView;
 
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
@@ -78,6 +79,7 @@
     protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
     protected @Mock LockIconView mLockIconView;
+    protected @Mock ImageView mLockIcon;
     protected @Mock AnimatedStateListDrawable mIconDrawable;
     protected @Mock Context mContext;
     protected @Mock Resources mResources;
@@ -146,8 +148,10 @@
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
 
-        mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
-        mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+        if (!Flags.sceneContainer()) {
+            mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+            mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+        }
 
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
@@ -172,8 +176,7 @@
                 mFeatureFlags,
                 mPrimaryBouncerInteractor,
                 mContext,
-                () -> mDeviceEntryInteractor,
-                mKosmos.getFakeSceneContainerFlags()
+                () -> mDeviceEntryInteractor
         );
     }
 
@@ -225,6 +228,7 @@
     protected void setupLockIconViewMocks() {
         when(mLockIconView.getResources()).thenReturn(mResources);
         when(mLockIconView.getContext()).thenReturn(mContext);
+        when(mLockIconView.getLockIcon()).thenReturn(mLockIcon);
     }
 
     protected void resetLockIconView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
index 8689842..255c7d9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.statusbar.StatusBarState;
 
 import org.junit.Test;
@@ -373,7 +374,6 @@
     @Test
     public void longPress_showBouncer_sceneContainerNotEnabled() {
         init(/* useMigrationFlag= */ false);
-        mKosmos.getFakeSceneContainerFlags().setEnabled(false);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -385,9 +385,9 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void longPress_showBouncer() {
         init(/* useMigrationFlag= */ false);
-        mKosmos.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -399,9 +399,9 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void longPress_falsingTriggered_doesNotShowBouncer() {
         init(/* useMigrationFlag= */ false);
-        mKosmos.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
 
         // WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index e076420..44207a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -477,9 +477,8 @@
         });
 
         // Verify the method is called in
-        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
-        // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
-        verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+        verify(mSpyController).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -594,10 +593,10 @@
         final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
                 - defaultMagnificationWindowSize / 2);
 
-        // This is called 5 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+        // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
         // created and we place the mirrored content as a child of the SurfaceView
-        // (3) the animation starts (4) the animation updates (5) the animation ends
-        verify(mTransaction, times(5))
+        // (3) the animation starts (4) the animation updates
+        verify(mTransaction, times(4))
                 .setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
     }
 
@@ -788,9 +787,8 @@
         waitForIdleSync();
 
         // Verify the method is called in
-        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
-        // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
-        verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+        verify(mSpyController).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -832,10 +830,8 @@
         deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
 
         // Verify the method is called in
-        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
-        // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
-        // the final duration time.
-        verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+        verify(mSpyController).updateWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
                 mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index a88654b..01e4d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -46,6 +46,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -459,6 +460,7 @@
         final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
         final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
 
+        reset(mWindowMagnifierCallback);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
                     targetCenterX, targetCenterY, mAnimationCallback);
@@ -491,6 +493,7 @@
         final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
         final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
 
+        reset(mWindowMagnifierCallback);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
                     centerX + 10, centerY + 10, mAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
index 8685384..2f4999b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index a127631..1e3b556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,7 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.display.DisplayHelper;
@@ -67,7 +67,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
+public class TouchMonitorTest extends SysuiTestCase {
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -78,7 +78,7 @@
         private final InputSession mInputSession;
         private final Lifecycle mLifecycle;
         private final LifecycleOwner mLifecycleOwner;
-        private final DreamOverlayTouchMonitor mMonitor;
+        private final TouchMonitor mMonitor;
         private final DefaultLifecycleObserver mLifecycleObserver;
         private final InputChannelCompat.InputEventListener mEventListener;
         private final GestureDetector.OnGestureListener mGestureListener;
@@ -88,7 +88,7 @@
         private final Rect mDisplayBounds = Mockito.mock(Rect.class);
         private final IWindowManager mIWindowManager;
 
-        Environment(Set<DreamTouchHandler> handlers) {
+        Environment(Set<TouchHandler> handlers) {
             mLifecycle = Mockito.mock(Lifecycle.class);
             mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
             mIWindowManager = Mockito.mock(IWindowManager.class);
@@ -104,7 +104,7 @@
             mDisplayHelper = Mockito.mock(DisplayHelper.class);
             when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
                     .thenReturn(mDisplayBounds);
-            mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor,
+            mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor,
                     mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
             mMonitor.init();
 
@@ -157,7 +157,7 @@
 
     @Test
     public void testReportedDisplayBounds() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
 
@@ -169,8 +169,8 @@
         // Verify display bounds passed into TouchHandler#getTouchInitiationRegion
         verify(touchHandler).getTouchInitiationRegion(
                 eq(environment.getDisplayBounds()), any(), any());
-        final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
-                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+        final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+                ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
 
         // Verify that display bounds provided from TouchSession#getBounds
@@ -180,7 +180,7 @@
 
     @Test
     public void testEntryTouchZone() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
         final Rect touchArea = new Rect(4, 4, 8 , 8);
 
         doAnswer(invocation -> {
@@ -208,10 +208,10 @@
 
     @Test
     public void testSessionCount() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
         final Rect touchArea = new Rect(4, 4, 8 , 8);
 
-        final DreamTouchHandler unzonedTouchHandler = createTouchHandler();
+        final TouchHandler unzonedTouchHandler = createTouchHandler();
         doAnswer(invocation -> {
             final Region region = (Region) invocation.getArguments()[1];
             region.set(touchArea);
@@ -227,13 +227,13 @@
         when(initialEvent.getY()).thenReturn(1.0f);
         environment.publishInputEvent(initialEvent);
 
-        ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
-                DreamTouchHandler.TouchSession.class);
+        ArgumentCaptor<TouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
+                TouchHandler.TouchSession.class);
 
         // Make sure only one active session.
         {
             verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture());
-            final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
+            final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
             assertThat(touchSession.getActiveSessionCount()).isEqualTo(1);
             touchSession.pop();
             environment.executeAll();
@@ -247,7 +247,7 @@
         // Make sure there are two active sessions.
         {
             verify(touchHandler).onSessionStart(touchSessionCaptor.capture());
-            final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
+            final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
             assertThat(touchSession.getActiveSessionCount()).isEqualTo(2);
             touchSession.pop();
         }
@@ -256,7 +256,7 @@
 
     @Test
     public void testNoActiveSessionWhenHandlerDisabled() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
         // disable the handler
         when(touchHandler.isEnabled()).thenReturn(false);
 
@@ -274,7 +274,7 @@
 
     @Test
     public void testInputEventPropagation() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -294,7 +294,7 @@
 
     @Test
     public void testInputEventPropagationAfterRemoval() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -303,7 +303,7 @@
         environment.publishInputEvent(initialEvent);
 
         // Ensure session started
-        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final TouchHandler.TouchSession session = captureSession(touchHandler);
         final InputChannelCompat.InputEventListener eventListener =
                 registerInputEventListener(session);
 
@@ -318,7 +318,7 @@
 
     @Test
     public void testInputGesturePropagation() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -337,7 +337,7 @@
 
     @Test
     public void testGestureConsumption() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -360,8 +360,8 @@
 
     @Test
     public void testBroadcast() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
-        final DreamTouchHandler touchHandler2 = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler2 = createTouchHandler();
         when(touchHandler2.isEnabled()).thenReturn(true);
 
         final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
@@ -386,7 +386,7 @@
 
     @Test
     public void testPush() throws InterruptedException, ExecutionException {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -394,13 +394,13 @@
         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
         environment.publishInputEvent(initialEvent);
 
-        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final TouchHandler.TouchSession session = captureSession(touchHandler);
         final InputChannelCompat.InputEventListener eventListener =
                 registerInputEventListener(session);
 
-        final ListenableFuture<DreamTouchHandler.TouchSession> frontSessionFuture = session.push();
+        final ListenableFuture<TouchHandler.TouchSession> frontSessionFuture = session.push();
         environment.executeAll();
-        final DreamTouchHandler.TouchSession frontSession = frontSessionFuture.get();
+        final TouchHandler.TouchSession frontSession = frontSessionFuture.get();
         final InputChannelCompat.InputEventListener frontEventListener =
                 registerInputEventListener(frontSession);
 
@@ -412,10 +412,10 @@
 
         Mockito.clearInvocations(eventListener, frontEventListener);
 
-        ListenableFuture<DreamTouchHandler.TouchSession> sessionFuture = frontSession.pop();
+        ListenableFuture<TouchHandler.TouchSession> sessionFuture = frontSession.pop();
         environment.executeAll();
 
-        DreamTouchHandler.TouchSession returnedSession = sessionFuture.get();
+        TouchHandler.TouchSession returnedSession = sessionFuture.get();
         assertThat(session == returnedSession).isTrue();
 
         environment.executeAll();
@@ -429,10 +429,10 @@
 
     @Test
     public void testPop() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
-        final DreamTouchHandler.TouchSession.Callback callback =
-                Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+        final TouchHandler.TouchSession.Callback callback =
+                Mockito.mock(TouchHandler.TouchSession.Callback.class);
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -440,7 +440,7 @@
         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
         environment.publishInputEvent(initialEvent);
 
-        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final TouchHandler.TouchSession session = captureSession(touchHandler);
         session.registerCallback(callback);
         session.pop();
         environment.executeAll();
@@ -450,7 +450,7 @@
 
     @Test
     public void testPauseWithNoActiveSessions() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -464,7 +464,7 @@
 
     @Test
     public void testDeferredPauseWithActiveSessions() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -481,8 +481,8 @@
         environment.publishInputEvent(event);
         verify(eventListener).onInputEvent(eq(event));
 
-        final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
-                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+        final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+                ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
 
         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
 
@@ -502,7 +502,7 @@
 
     @Test
     public void testDestroyWithActiveSessions() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -519,8 +519,8 @@
         environment.publishInputEvent(event);
         verify(eventListener).onInputEvent(eq(event));
 
-        final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
-                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+        final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+                ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
 
         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
 
@@ -535,19 +535,19 @@
 
     @Test
     public void testPilfering() {
-        final DreamTouchHandler touchHandler1 = createTouchHandler();
-        final DreamTouchHandler touchHandler2 = createTouchHandler();
+        final TouchHandler touchHandler1 = createTouchHandler();
+        final TouchHandler touchHandler2 = createTouchHandler();
         final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
                 .collect(Collectors.toCollection(HashSet::new)));
 
         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
         environment.publishInputEvent(initialEvent);
 
-        final DreamTouchHandler.TouchSession session1 = captureSession(touchHandler1);
+        final TouchHandler.TouchSession session1 = captureSession(touchHandler1);
         final GestureDetector.OnGestureListener gestureListener1 =
                 registerGestureListener(session1);
 
-        final DreamTouchHandler.TouchSession session2 = captureSession(touchHandler2);
+        final TouchHandler.TouchSession session2 = captureSession(touchHandler2);
         final GestureDetector.OnGestureListener gestureListener2 =
                 registerGestureListener(session2);
         when(gestureListener2.onDown(any())).thenReturn(true);
@@ -568,10 +568,10 @@
 
     @Test
     public void testOnRemovedCallbackOnStopMonitoring() {
-        final DreamTouchHandler touchHandler = createTouchHandler();
+        final TouchHandler touchHandler = createTouchHandler();
 
-        final DreamTouchHandler.TouchSession.Callback callback =
-                Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+        final TouchHandler.TouchSession.Callback callback =
+                Mockito.mock(TouchHandler.TouchSession.Callback.class);
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -579,7 +579,7 @@
         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
         environment.publishInputEvent(initialEvent);
 
-        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final TouchHandler.TouchSession session = captureSession(touchHandler);
         session.registerCallback(callback);
 
         environment.executeAll();
@@ -593,19 +593,19 @@
         verify(callback).onRemoved();
     }
 
-    public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
+    private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
                 GestureDetector.OnGestureListener.class);
-        final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
-                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+        final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
+                ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
         verify(handler).onSessionStart(sessionCaptor.capture());
         sessionCaptor.getValue().registerGestureListener(gestureListener);
 
         return gestureListener;
     }
 
-    public GestureDetector.OnGestureListener registerGestureListener(
-            DreamTouchHandler.TouchSession session) {
+    private GestureDetector.OnGestureListener registerGestureListener(
+            TouchHandler.TouchSession session) {
         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
                 GestureDetector.OnGestureListener.class);
         session.registerGestureListener(gestureListener);
@@ -613,8 +613,8 @@
         return gestureListener;
     }
 
-    public InputChannelCompat.InputEventListener registerInputEventListener(
-            DreamTouchHandler.TouchSession session) {
+    private InputChannelCompat.InputEventListener registerInputEventListener(
+            TouchHandler.TouchSession session) {
         final InputChannelCompat.InputEventListener eventListener = Mockito.mock(
                 InputChannelCompat.InputEventListener.class);
         session.registerInputListener(eventListener);
@@ -622,20 +622,20 @@
         return eventListener;
     }
 
-    public DreamTouchHandler.TouchSession captureSession(DreamTouchHandler handler) {
-        final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
-                ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+    private TouchHandler.TouchSession captureSession(TouchHandler handler) {
+        final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
+                ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
         verify(handler).onSessionStart(sessionCaptor.capture());
         return sessionCaptor.getValue();
     }
 
-    public InputChannelCompat.InputEventListener registerInputEventListener(
-            DreamTouchHandler handler) {
+    private InputChannelCompat.InputEventListener registerInputEventListener(
+            TouchHandler handler) {
         return registerInputEventListener(captureSession(handler));
     }
 
-    private DreamTouchHandler createTouchHandler() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+    private TouchHandler createTouchHandler() {
+        final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
         // enable the handler by default
         when(touchHandler.isEnabled()).thenReturn(true);
         return touchHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index f490f3c..cbad133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -41,7 +41,6 @@
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
@@ -109,7 +108,6 @@
             headsUpManager,
             powerInteractor,
             activeNotificationsInteractor,
-            kosmos.sceneContainerFlags,
             kosmos::sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 30c5e6e..d3cc232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -78,7 +78,6 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.phone.dozeServiceHost
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -239,7 +238,6 @@
                 testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
-                kosmos.fakeSceneContainerFlags,
                 kosmos.sceneInteractor,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 238a76e..415da02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -77,7 +77,6 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.phone.dozeServiceHost
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -236,7 +235,6 @@
                 testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
-                kosmos.fakeSceneContainerFlags,
                 kosmos.sceneInteractor,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
new file mode 100644
index 0000000..8a1a082
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AudioSharingInteractorTest : SysuiTestCase() {
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val bluetoothState = MutableStateFlow(false)
+    private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+    @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
+    @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+    @Mock private lateinit var deviceItem: DeviceItem
+    private lateinit var mockitoSession: StaticMockitoSession
+    private lateinit var audioSharingInteractor: AudioSharingInteractor
+
+    @Before
+    fun setUp() {
+        mockitoSession =
+            mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+        whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
+        whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+        audioSharingInteractor =
+            AudioSharingInteractor(
+                localBluetoothManager,
+                bluetoothStateInteractor,
+                deviceItemInteractor,
+                testScope.backgroundScope,
+                testDispatcher,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+    }
+
+    @Test
+    fun testButtonStateUpdate_bluetoothOff_returnGone() {
+        testScope.runTest {
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+
+            assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_noDevice_returnGone() {
+        testScope.runTest {
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            runCurrent()
+
+            assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
+        testScope.runTest {
+            whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
+
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            deviceItemUpdate.emit(listOf())
+            runCurrent()
+
+            assertThat(actual)
+                .isEqualTo(
+                    AudioSharingButtonState.Visible(
+                        R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_hasSource_returnGone() {
+        testScope.runTest {
+            whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+            whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            whenever(
+                    BluetoothUtils.hasConnectedBroadcastSource(
+                        cachedBluetoothDevice,
+                        localBluetoothManager
+                    )
+                )
+                .thenReturn(true)
+
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            deviceItemUpdate.emit(listOf(deviceItem))
+            runCurrent()
+
+            assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
+        testScope.runTest {
+            whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+            whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            whenever(
+                    BluetoothUtils.hasConnectedBroadcastSource(
+                        cachedBluetoothDevice,
+                        localBluetoothManager
+                    )
+                )
+                .thenReturn(false)
+            whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            deviceItemUpdate.emit(listOf(deviceItem))
+            runCurrent()
+
+            assertThat(actual)
+                .isEqualTo(
+                    AudioSharingButtonState.Visible(
+                        R.string.quick_settings_bluetooth_audio_sharing_button
+                    )
+                )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index a8f82ed..6fe7d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -41,7 +42,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class BluetoothStateInteractorTest : SysuiTestCase() {
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
-    private val testScope = TestScope()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
 
@@ -52,7 +54,12 @@
     @Before
     fun setUp() {
         bluetoothStateInteractor =
-            BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope)
+            BluetoothStateInteractor(
+                localBluetoothManager,
+                logger,
+                testScope.backgroundScope,
+                testDispatcher
+            )
         `when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
     }
 
@@ -61,7 +68,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(true)
 
-            assertThat(bluetoothStateInteractor.isBluetoothEnabled).isTrue()
+            assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isTrue()
         }
     }
 
@@ -70,7 +77,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(false)
 
-            assertThat(bluetoothStateInteractor.isBluetoothEnabled).isFalse()
+            assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isFalse()
         }
     }
 
@@ -79,7 +86,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(false)
 
-            bluetoothStateInteractor.isBluetoothEnabled = true
+            bluetoothStateInteractor.setBluetoothEnabled(true)
             verify(bluetoothAdapter).enable()
             verify(logger)
                 .logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString())
@@ -91,7 +98,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(false)
 
-            bluetoothStateInteractor.isBluetoothEnabled = false
+            bluetoothStateInteractor.setBluetoothEnabled(false)
             verify(bluetoothAdapter, never()).enable()
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 12dfe97..62c98b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -110,7 +110,6 @@
             BluetoothTileDialogDelegate(
                 uiProperties,
                 CONTENT_HEIGHT,
-                ENABLED,
                 bluetoothTileDialogCallback,
                 {},
                 dispatcher,
@@ -211,7 +210,6 @@
             BluetoothTileDialogDelegate(
                     uiProperties,
                     CONTENT_HEIGHT,
-                    ENABLED,
                     bluetoothTileDialogCallback,
                     {},
                     dispatcher,
@@ -267,7 +265,6 @@
                 BluetoothTileDialogDelegate(
                         BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
                         cachedHeight,
-                        ENABLED,
                         bluetoothTileDialogCallback,
                         {},
                         dispatcher,
@@ -291,7 +288,6 @@
                 BluetoothTileDialogDelegate(
                         BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
                         MATCH_PARENT,
-                        ENABLED,
                         bluetoothTileDialogCallback,
                         {},
                         dispatcher,
@@ -315,7 +311,6 @@
                 BluetoothTileDialogDelegate(
                         BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
                         MATCH_PARENT,
-                        ENABLED,
                         bluetoothTileDialogCallback,
                         {},
                         dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 6d99c5b..b05d959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -52,7 +52,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
@@ -74,7 +73,7 @@
 
     @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
 
-    @Mock private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor
+    @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
 
     @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
 
@@ -92,6 +91,8 @@
 
     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
 
+    @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
+
     @Mock
     private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
         BluetoothTileDialogDelegate.Factory
@@ -115,7 +116,12 @@
         bluetoothTileDialogViewModel =
             BluetoothTileDialogViewModel(
                 deviceItemInteractor,
-                bluetoothStateInteractor,
+                BluetoothStateInteractor(
+                    localBluetoothManager,
+                    bluetoothTileDialogLogger,
+                    testScope.backgroundScope,
+                    dispatcher
+                ),
                 // TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
                 BluetoothAutoOnInteractor(
                     BluetoothAutoOnRepository(
@@ -125,6 +131,7 @@
                         dispatcher
                     )
                 ),
+                audioSharingInteractor,
                 mDialogTransitionAnimator,
                 activityStarter,
                 uiEventLogger,
@@ -135,20 +142,9 @@
                 mBluetoothTileDialogDelegateDelegateFactory
             )
         whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
-        whenever(bluetoothStateInteractor.bluetoothStateUpdate)
-            .thenReturn(MutableStateFlow(null).asStateFlow())
         whenever(deviceItemInteractor.deviceItemUpdateRequest)
             .thenReturn(MutableStateFlow(Unit).asStateFlow())
-        whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
-        whenever(
-                mBluetoothTileDialogDelegateDelegateFactory.create(
-                    any(),
-                    anyInt(),
-                    ArgumentMatchers.anyBoolean(),
-                    any(),
-                    any()
-                )
-            )
+        whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
             .thenReturn(bluetoothTileDialogDelegate)
         whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
         whenever(sysuiDialog.context).thenReturn(mContext)
@@ -159,6 +155,8 @@
         whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
         whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
             .thenReturn(getMutableStateFlow(false))
+        whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
+            .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
     }
 
     @Test
@@ -201,15 +199,6 @@
     }
 
     @Test
-    fun testShowDialog_withBluetoothStateValue() {
-        testScope.runTest {
-            bluetoothTileDialogViewModel.showDialog(null)
-
-            verify(bluetoothStateInteractor).bluetoothStateUpdate
-        }
-    }
-
-    @Test
     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
         testScope.runTest {
             whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index eb735cb..daf4a3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -281,7 +281,7 @@
             override fun isFilterMatched(
                 context: Context,
                 cachedDevice: CachedBluetoothDevice,
-                audioManager: AudioManager?
+                audioManager: AudioManager
             ) = isFilterMatchFunc(cachedDevice)
 
             override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
new file mode 100644
index 0000000..d118cc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.ui.view
+
+import android.app.Dialog
+import android.graphics.Insets
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.Window
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MirroringConfirmationDialogDelegateTest : SysuiTestCase() {
+
+    private lateinit var underTest: MirroringConfirmationDialogDelegate
+
+    private val onStartMirroringCallback = mock<View.OnClickListener>()
+    private val onCancelCallback = mock<View.OnClickListener>()
+    private val windowDecorView: View = mock {}
+    private val windowInsetsAnimationCallbackCaptor =
+        ArgumentCaptor.forClass(WindowInsetsAnimation.Callback::class.java)
+    private val dialog: Dialog =
+        mock<Dialog> {
+            var view: View? = null
+            whenever(setContentView(any<Int>())).then {
+                view =
+                    LayoutInflater.from(this@MirroringConfirmationDialogDelegateTest.context)
+                        .inflate(it.arguments[0] as Int, null, false)
+                Unit
+            }
+            whenever(requireViewById<View>(any<Int>())).then {
+                view?.requireViewById(it.arguments[0] as Int)
+            }
+            val window: Window = mock { whenever(decorView).thenReturn(windowDecorView) }
+            whenever(this.window).thenReturn(window)
+        }
+
+    @Before
+    fun setUp() {
+        underTest =
+            MirroringConfirmationDialogDelegate(
+                context = context,
+                showConcurrentDisplayInfo = false,
+                onStartMirroringClickListener = onStartMirroringCallback,
+                onCancelMirroring = onCancelCallback,
+                navbarBottomInsetsProvider = { 0 },
+            )
+    }
+
+    @Test
+    fun startMirroringButton_clicked_callsCorrectCallback() {
+        underTest.onCreate(dialog, null)
+
+        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+        verify(onStartMirroringCallback).onClick(any())
+        verify(onCancelCallback, never()).onClick(any())
+    }
+
+    @Test
+    fun cancelButton_clicked_callsCorrectCallback() {
+        underTest.onCreate(dialog, null)
+
+        dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+        verify(onCancelCallback).onClick(any())
+        verify(onStartMirroringCallback, never()).onClick(any())
+    }
+
+    @Test
+    fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
+        underTest.onCreate(dialog, null)
+        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+        underTest.onStop(dialog)
+
+        verify(onCancelCallback, never()).onClick(any())
+        verify(onStartMirroringCallback).onClick(any())
+    }
+
+    @Test
+    fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
+        underTest.onCreate(dialog, null)
+        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+        underTest.onStop(dialog)
+
+        verify(onCancelCallback, never()).onClick(any())
+        verify(onStartMirroringCallback).onClick(any())
+    }
+
+    @Test
+    fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+        underTest.onCreate(dialog, null)
+        underTest.onStart(dialog)
+
+        val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+
+        triggerInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    @Test
+    fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+        underTest.onCreate(dialog, null)
+        underTest.onStart(dialog)
+
+        val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+        triggerInsetsChanged(WindowInsets.Type.ime(), insets)
+
+        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+            .isNotEqualTo(TEST_BOTTOM_INSETS)
+    }
+
+    private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+        return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+    }
+
+    private fun triggerInsetsChanged(type: Int, insets: WindowInsets) {
+        verify(windowDecorView)
+            .setWindowInsetsAnimationCallback(capture(windowInsetsAnimationCallbackCaptor))
+        windowInsetsAnimationCallbackCaptor.value.onProgress(
+            insets,
+            listOf(WindowInsetsAnimation(type, Interpolators.INSTANT, 0))
+        )
+    }
+
+    private companion object {
+        const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
deleted file mode 100644
index 30519b0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.display.ui.view
-
-import android.graphics.Insets
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.WindowInsets
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.res.R
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MirroringConfirmationDialogTest : SysuiTestCase() {
-
-    private lateinit var dialog: MirroringConfirmationDialog
-
-    private val onStartMirroringCallback = mock<View.OnClickListener>()
-    private val onCancelCallback = mock<View.OnClickListener>()
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        dialog =
-            MirroringConfirmationDialog(
-                context,
-                onStartMirroringCallback,
-                onCancelCallback,
-                navbarBottomInsetsProvider = { 0 },
-            )
-    }
-
-    @Test
-    fun startMirroringButton_clicked_callsCorrectCallback() {
-        dialog.show()
-
-        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
-        verify(onStartMirroringCallback).onClick(any())
-        verify(onCancelCallback, never()).onClick(any())
-    }
-
-    @Test
-    fun cancelButton_clicked_callsCorrectCallback() {
-        dialog.show()
-
-        dialog.requireViewById<View>(R.id.cancel).callOnClick()
-
-        verify(onCancelCallback).onClick(any())
-        verify(onStartMirroringCallback, never()).onClick(any())
-    }
-
-    @Test
-    fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
-        dialog.show()
-        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
-        dialog.cancel()
-
-        verify(onCancelCallback, never()).onClick(any())
-        verify(onStartMirroringCallback).onClick(any())
-    }
-
-    @Test
-    fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
-        dialog.show()
-        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
-        dialog.dismiss()
-
-        verify(onCancelCallback, never()).onClick(any())
-        verify(onStartMirroringCallback).onClick(any())
-    }
-
-    @Test
-    fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
-        dialog.show()
-
-        val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
-        dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
-
-        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
-            .isEqualTo(TEST_BOTTOM_INSETS)
-    }
-
-    @Test
-    fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
-        dialog.show()
-
-        val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
-        dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
-
-        assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
-            .isNotEqualTo(TEST_BOTTOM_INSETS)
-    }
-
-    private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
-        return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
-    }
-
-    @After
-    fun teardown() {
-        if (::dialog.isInitialized) {
-            dialog.dismiss()
-        }
-    }
-
-    private companion object {
-        const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 318227f..709f779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,8 +25,8 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
 import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
+import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
 import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
 import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -102,7 +102,6 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -222,7 +221,6 @@
     private @Mock DreamViewModel mDreamViewModel;
     private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel;
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
-    private @Mock SceneContainerFlags mSceneContainerFlags;
 
     private FakeFeatureFlags mFeatureFlags;
     private final int mDefaultUserId = 100;
@@ -270,7 +268,6 @@
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
                 mUserTracker,
-                mSceneContainerFlags,
                 mKosmos::getCommunalInteractor);
         mFeatureFlags = new FakeFeatureFlags();
         mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
@@ -1243,7 +1240,7 @@
                 mock(WindowManagerOcclusionManager.class));
         mViewMediator.start();
 
-        mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
+        mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
     }
 
     private void captureKeyguardStateControllerCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b0aace6..b50d248 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.utils.GlobalWindowManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -63,8 +62,8 @@
 
         val withDeps =
             KeyguardInteractorFactory.create(
-                repository = keyguardRepository,
                 featureFlags = featureFlags,
+                repository = keyguardRepository,
             )
         val keyguardInteractor = withDeps.keyguardInteractor
         resourceTrimmer =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 9266af4..dc7f372 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -40,7 +41,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -87,7 +87,6 @@
     @Before
     fun setup() {
         mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
-        kosmos.fakeSceneContainerFlags.enabled = false
 
         primaryBouncerInteractor =
             PrimaryBouncerInteractor(
@@ -127,7 +126,6 @@
                 testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
-                kosmos.fakeSceneContainerFlags,
                 kosmos.sceneInteractor,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
@@ -168,15 +166,14 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun updatesShowIndicatorForDeviceEntry_onBouncerSceneActive() =
         testScope.runTest {
-            kosmos.fakeSceneContainerFlags.enabled = true
             underTest =
                 DeviceEntrySideFpsOverlayInteractor(
                     testScope.backgroundScope,
                     mContext,
                     deviceEntryFingerprintAuthRepository,
-                    kosmos.fakeSceneContainerFlags,
                     kosmos.sceneInteractor,
                     primaryBouncerInteractor,
                     alternateBouncerInteractor,
@@ -196,15 +193,14 @@
         }
 
     @Test
+    @EnableSceneContainer
     fun updatesShowIndicatorForDeviceEntry_onBouncerSceneInactive() =
         testScope.runTest {
-            kosmos.fakeSceneContainerFlags.enabled = true
             underTest =
                 DeviceEntrySideFpsOverlayInteractor(
                     testScope.backgroundScope,
                     mContext,
                     deviceEntryFingerprintAuthRepository,
-                    kosmos.fakeSceneContainerFlags,
                     kosmos.sceneInteractor,
                     primaryBouncerInteractor,
                     alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index 3f05bfa..9ccf212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -227,4 +228,50 @@
                 { it == KeyguardSurfaceBehindModel(alpha = 0f) },
             )
         }
+
+    @Test
+    fun notificationLaunchFromLockscreen_isAnimatingSurfaceTrue() =
+        testScope.runTest {
+            val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+            assertThat(isAnimatingSurface).isTrue()
+        }
+
+    @Test
+    fun notificationLaunchFromGone_isAnimatingSurfaceFalse() =
+        testScope.runTest {
+            val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+            assertThat(isAnimatingSurface).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index 66aa572..5e3a142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -113,7 +113,8 @@
                 id = "WEATHER_CLOCK",
                 name = "",
                 description = "",
-                useAlternateSmartspaceAODTransition = true
+                useAlternateSmartspaceAODTransition = true,
+                useCustomClockScene = true
             )
         whenever(clock.config).thenReturn(clockConfig)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index fe8fdc0..8f73811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -50,6 +50,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
@@ -76,9 +77,10 @@
 @TestableLooper.RunWithLooper
 class MediaDataFilterImplTest : SysuiTestCase() {
 
+    @Mock private lateinit var listener: MediaDataProcessor.Listener
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var broadcastSender: BroadcastSender
-    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var mediaDataProcessor: MediaDataProcessor
     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var executor: Executor
     @Mock private lateinit var smartspaceData: SmartspaceMediaData
@@ -101,7 +103,7 @@
         MediaPlayerData.clear()
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         testScope = TestScope()
-        repository = MediaFilterRepository()
+        repository = MediaFilterRepository(FakeSystemClock())
         mediaDataFilter =
             MediaDataFilterImpl(
                 context,
@@ -114,7 +116,8 @@
                 mediaFlags,
                 repository,
             )
-        mediaDataFilter.mediaDataManager = mediaDataManager
+        mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+        mediaDataFilter.addListener(listener)
 
         // Start all tests as main user
         setUser(USER_MAIN)
@@ -167,6 +170,8 @@
 
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
 
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
             assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
         }
 
@@ -178,6 +183,8 @@
 
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
 
+            verify(listener, never())
+                .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
             assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
         }
 
@@ -196,6 +203,7 @@
             mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
             mediaDataFilter.onMediaDataRemoved(KEY)
 
+            verify(listener).onMediaDataRemoved(eq(KEY))
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
         }
 
@@ -208,6 +216,7 @@
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
             mediaDataFilter.onMediaDataRemoved(KEY)
 
+            verify(listener, never()).onMediaDataRemoved(eq(KEY))
             assertThat(mediaDataLoadedStates).isEmpty()
         }
 
@@ -226,6 +235,7 @@
             setUser(USER_GUEST)
 
             // THEN we should remove the main user's media
+            verify(listener).onMediaDataRemoved(eq(KEY))
             assertThat(mediaDataLoadedStates).isEmpty()
         }
 
@@ -243,6 +253,20 @@
             // and we switch to guest user
             setUser(USER_GUEST)
 
+            // THEN we should add back the guest user media
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
+
+            // but not the main user's
+            verify(listener, never())
+                .onMediaDataLoaded(
+                    eq(KEY),
+                    any(),
+                    eq(dataMain),
+                    anyBoolean(),
+                    anyInt(),
+                    anyBoolean()
+                )
             assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel)
             assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel)
         }
@@ -261,6 +285,7 @@
 
             val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
             // THEN we should remove the private profile media
+            verify(listener).onMediaDataRemoved(eq(KEY_ALT))
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
         }
 
@@ -481,7 +506,7 @@
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
         mediaDataFilter.onSwipeToDismiss()
 
-        verify(mediaDataManager).setInactive(eq(KEY), eq(true), eq(true))
+        verify(mediaDataProcessor).setInactive(eq(KEY), eq(true), eq(true))
     }
 
     @Test
@@ -507,6 +532,8 @@
                 )
                 .isTrue()
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -534,6 +561,9 @@
                 )
                 .isFalse()
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+            verify(listener, never())
+                .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -563,6 +593,8 @@
                 )
                 .isTrue()
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -592,6 +624,7 @@
                 )
                 .isFalse()
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -614,6 +647,8 @@
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
             // AND we get a smartspace signal
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -629,6 +664,9 @@
                 )
                 .isFalse()
             assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+            verify(listener, never())
+                .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
+            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger, never()).logRecommendationActivated(any(), any(), any())
         }
@@ -649,12 +687,15 @@
             val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
             // AND we get a smartspace signal
             runCurrent()
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
             // THEN we should treat the media as active instead
+            val dataCurrentAndActive = dataCurrent.copy(active = true)
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -664,8 +705,18 @@
                     )
                 )
                 .isTrue()
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(KEY),
+                    eq(KEY),
+                    eq(dataCurrentAndActive),
+                    eq(true),
+                    eq(100),
+                    eq(true)
+                )
             // Smartspace update shouldn't be propagated for the empty rec list.
             assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
+            verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
             verify(logger, never()).logRecommendationAdded(any(), any())
             verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
         }
@@ -687,12 +738,24 @@
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
             assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
             // AND we get a smartspace signal
             runCurrent()
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
             // THEN we should treat the media as active instead
+            val dataCurrentAndActive = dataCurrent.copy(active = true)
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(KEY),
+                    eq(KEY),
+                    eq(dataCurrentAndActive),
+                    eq(true),
+                    eq(100),
+                    eq(true)
+                )
             assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -704,6 +767,8 @@
                 .isTrue()
             // Smartspace update should also be propagated but not prioritized.
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
             verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
         }
@@ -721,6 +786,7 @@
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
+            verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -746,15 +812,29 @@
             val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
             runCurrent()
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
+            val dataCurrentAndActive = dataCurrent.copy(active = true)
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(KEY),
+                    eq(KEY),
+                    eq(dataCurrentAndActive),
+                    eq(true),
+                    eq(100),
+                    eq(true)
+                )
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
 
             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
+            verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -781,6 +861,8 @@
 
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -813,12 +895,18 @@
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
 
             // And an inactive recommendation is loaded
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
             // Smartspace is loaded but the media stays inactive
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+            verify(listener, never())
+                .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -846,8 +934,8 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
         mediaDataFilter.onSwipeToDismiss()
 
-        verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY))
-        verify(mediaDataManager, never())
+        verify(mediaDataProcessor).setRecommendationInactive(eq(SMARTSPACE_KEY))
+        verify(mediaDataProcessor, never())
             .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
     }
 
@@ -866,6 +954,8 @@
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
 
             // AND we get a smartspace signal with extra to trigger resume
@@ -875,6 +965,16 @@
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
             // THEN we should treat the media as active instead
+            val dataCurrentAndActive = dataCurrent.copy(active = true)
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(KEY),
+                    eq(KEY),
+                    eq(dataCurrentAndActive),
+                    eq(true),
+                    eq(100),
+                    eq(true)
+                )
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -886,6 +986,8 @@
                 .isTrue()
             // And update the smartspace data state, but not prioritized
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
         }
 
     @Test
@@ -901,6 +1003,8 @@
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
+            verify(listener)
+                .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
             assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
 
             // AND we get a smartspace signal with extra to not trigger resume
@@ -908,7 +1012,12 @@
             whenever(cardAction.extras).thenReturn(extras)
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
+            // THEN listeners are not updated to show media
+            verify(listener, never())
+                .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
             // But the smartspace update is still propagated
+            verify(listener)
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
             assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 5c275b4..ffb50c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -210,7 +210,7 @@
         )
         testDispatcher = UnconfinedTestDispatcher()
         testScope = TestScope(testDispatcher)
-        mediaFilterRepository = MediaFilterRepository()
+        mediaFilterRepository = MediaFilterRepository(clock)
         mediaDataRepository = MediaDataRepository(mediaFlags, dumpManager)
         mediaDataProcessor =
             MediaDataProcessor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index a73bb2c..e5d3082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -16,29 +16,54 @@
 
 package com.android.systemui.media.controls.ui.controller
 
+import android.animation.AnimatorSet
+import android.content.Context
 import android.content.res.Configuration
 import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Interpolator
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.CachingIconView
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionViewState
 import com.android.systemui.util.animation.WidgetState
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.floatThat
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -55,6 +80,31 @@
         com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
     private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
     private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+    private val clock = FakeSystemClock()
+    private lateinit var mainExecutor: FakeExecutor
+    private lateinit var seekBar: SeekBar
+    private lateinit var multiRippleView: MultiRippleView
+    private lateinit var turbulenceNoiseView: TurbulenceNoiseView
+    private lateinit var loadingEffectView: LoadingEffectView
+    private lateinit var settings: ImageButton
+    private lateinit var cancel: View
+    private lateinit var cancelText: TextView
+    private lateinit var dismiss: FrameLayout
+    private lateinit var dismissText: TextView
+    private lateinit var titleText: TextView
+    private lateinit var artistText: TextView
+    private lateinit var explicitIndicator: CachingIconView
+    private lateinit var seamless: ViewGroup
+    private lateinit var seamlessButton: View
+    private lateinit var seamlessIcon: ImageView
+    private lateinit var seamlessText: TextView
+    private lateinit var scrubbingElapsedTimeView: TextView
+    private lateinit var scrubbingTotalTimeView: TextView
+    private lateinit var actionPlayPause: ImageButton
+    private lateinit var actionNext: ImageButton
+    private lateinit var actionPrev: ImageButton
+    @Mock private lateinit var seamlessBackground: RippleDrawable
+    @Mock private lateinit var albumView: ImageView
     @Mock lateinit var logger: MediaViewLogger
     @Mock private lateinit var mockViewState: TransitionViewState
     @Mock private lateinit var mockCopiedState: TransitionViewState
@@ -64,6 +114,14 @@
     @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
     @Mock private lateinit var mediaContainerWidgetState: WidgetState
     @Mock private lateinit var mediaFlags: MediaFlags
+    @Mock private lateinit var seekBarViewModel: SeekBarViewModel
+    @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
+    @Mock private lateinit var globalSettings: GlobalSettings
+    @Mock private lateinit var viewHolder: MediaViewHolder
+    @Mock private lateinit var view: TransitionLayout
+    @Mock private lateinit var mockAnimator: AnimatorSet
+    @Mock private lateinit var gutsViewHolder: GutsViewHolder
+    @Mock private lateinit var gutsText: TextView
 
     private val delta = 0.1F
 
@@ -72,14 +130,30 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        mainExecutor = FakeExecutor(clock)
         mediaViewController =
-            MediaViewController(
-                context,
-                configurationController,
-                mediaHostStatesManager,
-                logger,
-                mediaFlags,
-            )
+            object :
+                MediaViewController(
+                    context,
+                    configurationController,
+                    mediaHostStatesManager,
+                    logger,
+                    seekBarViewModel,
+                    mainExecutor,
+                    mediaFlags,
+                    globalSettings,
+                ) {
+                override fun loadAnimator(
+                    context: Context,
+                    animId: Int,
+                    motionInterpolator: Interpolator?,
+                    vararg targets: View?
+                ): AnimatorSet {
+                    return mockAnimator
+                }
+            }
+        initGutsViewHolderMocks()
+        initMediaViewHolderMocks()
     }
 
     @Test
@@ -299,4 +373,270 @@
         verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
         verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
     }
+
+    @Test
+    fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        getEnabledChangeListener().onEnabledChanged(enabled = true)
+        getEnabledChangeListener().onEnabledChanged(enabled = false)
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+            .isEqualTo(ConstraintSet.INVISIBLE)
+    }
+
+    @Test
+    fun attachPlayer_seekBarEnabled_seekBarVisible() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        getEnabledChangeListener().onEnabledChanged(enabled = true)
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+            .isEqualTo(ConstraintSet.VISIBLE)
+    }
+
+    @Test
+    fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        getEnabledChangeListener().onEnabledChanged(enabled = true)
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+            .isEqualTo(ConstraintSet.VISIBLE)
+
+        getEnabledChangeListener().onEnabledChanged(enabled = false)
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+            .isEqualTo(ConstraintSet.INVISIBLE)
+    }
+
+    @Test
+    fun attachPlayer_notScrubbing_scrubbingViewsGone() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        mediaViewController.canShowScrubbingTime = true
+        getScrubbingChangeListener().onScrubbingChanged(true)
+        getScrubbingChangeListener().onScrubbingChanged(false)
+        mainExecutor.runAllReady()
+
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+    }
+
+    @Test
+    fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        mediaViewController.canShowScrubbingTime = false
+        getScrubbingChangeListener().onScrubbingChanged(true)
+        mainExecutor.runAllReady()
+
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+    }
+
+    @Test
+    fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        mediaViewController.setUpNextButtonInfo(true)
+        mediaViewController.setUpPrevButtonInfo(false)
+        getScrubbingChangeListener().onScrubbingChanged(true)
+        mainExecutor.runAllReady()
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+            .isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+    }
+
+    @Test
+    fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        mediaViewController.setUpNextButtonInfo(false)
+        mediaViewController.setUpPrevButtonInfo(true)
+        getScrubbingChangeListener().onScrubbingChanged(true)
+        mainExecutor.runAllReady()
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+            .isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+    }
+
+    @Test
+    fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        mediaViewController.setUpNextButtonInfo(true)
+        mediaViewController.setUpPrevButtonInfo(true)
+        mediaViewController.canShowScrubbingTime = true
+        getScrubbingChangeListener().onScrubbingChanged(true)
+        mainExecutor.runAllReady()
+
+        // Only in expanded, we should show the scrubbing times and hide prev+next
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.VISIBLE)
+    }
+
+    @Test
+    fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
+        whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+        mediaViewController.attachPlayer(viewHolder)
+        mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
+        mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE)
+        mediaViewController.canShowScrubbingTime = true
+
+        getScrubbingChangeListener().onScrubbingChanged(true)
+        mainExecutor.runAllReady()
+
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+            .isEqualTo(ConstraintSet.INVISIBLE)
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+            .isEqualTo(ConstraintSet.INVISIBLE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.VISIBLE)
+
+        getScrubbingChangeListener().onScrubbingChanged(false)
+        mainExecutor.runAllReady()
+
+        // Only in expanded, we should hide the scrubbing times and show prev+next
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+            .isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+            .isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+        assertThat(
+                mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+            )
+            .isEqualTo(ConstraintSet.GONE)
+    }
+
+    private fun initGutsViewHolderMocks() {
+        settings = ImageButton(context)
+        cancel = View(context)
+        cancelText = TextView(context)
+        dismiss = FrameLayout(context)
+        dismissText = TextView(context)
+        whenever(gutsViewHolder.gutsText).thenReturn(gutsText)
+        whenever(gutsViewHolder.settings).thenReturn(settings)
+        whenever(gutsViewHolder.cancel).thenReturn(cancel)
+        whenever(gutsViewHolder.cancelText).thenReturn(cancelText)
+        whenever(gutsViewHolder.dismiss).thenReturn(dismiss)
+        whenever(gutsViewHolder.dismissText).thenReturn(dismissText)
+    }
+
+    private fun initMediaViewHolderMocks() {
+        titleText = TextView(context)
+        artistText = TextView(context)
+        explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator }
+        seamless = FrameLayout(context)
+        seamless.foreground = seamlessBackground
+        seamlessButton = View(context)
+        seamlessIcon = ImageView(context)
+        seamlessText = TextView(context)
+        seekBar = SeekBar(context).also { it.id = R.id.media_progress_bar }
+
+        actionPlayPause = ImageButton(context).also { it.id = R.id.actionPlayPause }
+        actionPrev = ImageButton(context).also { it.id = R.id.actionPrev }
+        actionNext = ImageButton(context).also { it.id = R.id.actionNext }
+        scrubbingElapsedTimeView =
+            TextView(context).also { it.id = R.id.media_scrubbing_elapsed_time }
+        scrubbingTotalTimeView = TextView(context).also { it.id = R.id.media_scrubbing_total_time }
+
+        multiRippleView = MultiRippleView(context, null)
+        turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        loadingEffectView = LoadingEffectView(context, null)
+
+        whenever(viewHolder.player).thenReturn(view)
+        whenever(view.context).thenReturn(context)
+        whenever(viewHolder.albumView).thenReturn(albumView)
+        whenever(albumView.foreground).thenReturn(Mockito.mock(Drawable::class.java))
+        whenever(viewHolder.titleText).thenReturn(titleText)
+        whenever(viewHolder.artistText).thenReturn(artistText)
+        whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator)
+        whenever(seamlessBackground.getDrawable(0))
+            .thenReturn(Mockito.mock(GradientDrawable::class.java))
+        whenever(viewHolder.seamless).thenReturn(seamless)
+        whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
+        whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon)
+        whenever(viewHolder.seamlessText).thenReturn(seamlessText)
+        whenever(viewHolder.seekBar).thenReturn(seekBar)
+        whenever(viewHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
+        whenever(viewHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
+        whenever(viewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
+        whenever(seekBarViewModel.progress).thenReturn(seekBarData)
+
+        // Action buttons
+        whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
+        whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
+        whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
+        whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
+
+        whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+        whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
+        whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView)
+    }
+
+    private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
+        withArgCaptor {
+            verify(seekBarViewModel).setScrubbingChangeListener(capture())
+        }
+
+    private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
+        verify(seekBarViewModel).setEnabledChangeListener(capture())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ca403e0..9bb21f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -123,11 +123,22 @@
         mMediaControllers.add(mMediaController);
         when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
 
-        mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mContext,
+                        TEST_PACKAGE,
+                        mContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         // Using a fake package will cause routing operations to fail, so we intercept
         // scanning-related operations.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index c9eb67e..2e6388a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -124,11 +124,22 @@
         when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
                 BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
 
-        mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mContext,
+                        TEST_PACKAGE,
+                        mContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
                 mBroadcastSender, mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 980eb59..4eb0038 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -194,11 +194,22 @@
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
                 mCachedBluetoothDeviceManager);
 
-        mMediaOutputController = new MediaOutputController(mSpyContext, mPackageName,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        mPackageName,
+                        mContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
@@ -276,11 +287,22 @@
 
     @Test
     public void start_withoutPackageName_verifyMediaControllerInit() {
-        mMediaOutputController = new MediaOutputController(mSpyContext, null,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        null,
+                        mContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         mMediaOutputController.start(mCb);
 
@@ -306,11 +328,22 @@
 
     @Test
     public void stop_withoutPackageName_verifyMediaControllerDeinit() {
-        mMediaOutputController = new MediaOutputController(mSpyContext, null,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        null,
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         mMediaOutputController.start(mCb);
 
@@ -550,12 +583,22 @@
 
     @Test
     public void getAppSourceName_packageNameIsNull_returnsNull() {
-        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
-                "",
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        MediaOutputController testMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        "",
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
 
@@ -573,12 +616,22 @@
 
     @Test
     public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
-        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
-                "",
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        MediaOutputController testMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        "",
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
 
@@ -609,12 +662,22 @@
 
     @Test
     public void addDeviceToPlayMedia_callsLocalMediaManager() {
-        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
-                null,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        MediaOutputController testMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        null,
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
         testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
@@ -625,12 +688,22 @@
 
     @Test
     public void removeDeviceFromPlayMedia_callsLocalMediaManager() {
-        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
-                null,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        MediaOutputController testMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        null,
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
         testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
@@ -894,11 +967,22 @@
 
     @Test
     public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
-        mMediaOutputController = new MediaOutputController(mSpyContext, null,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        null,
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
@@ -1085,12 +1169,22 @@
 
     @Test
     public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() {
-        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
-                null,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        MediaOutputController testMediaOutputController =
+                new MediaOutputController(
+                        mSpyContext,
+                        null,
+                        mSpyContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
 
         testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 83def8e4..3b6a88a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -61,7 +62,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, times(1))
-                .createAndShow(getContext().getPackageName(), false, null);
+                .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -72,7 +73,8 @@
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -82,7 +84,8 @@
         Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -95,7 +98,8 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -108,9 +112,10 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, times(1))
-                .createAndShow(getContext().getPackageName(), true, null);
+                .createAndShow(eq(getContext().getPackageName()), eq(true), any());
     }
 
     @Test
@@ -121,7 +126,8 @@
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -133,7 +139,8 @@
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -145,7 +152,8 @@
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -155,7 +163,8 @@
         Intent intent = new Intent("UnKnown Action");
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
-        verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+        verify(mMockMediaOutputDialogManager, never())
+                .createAndShow(any(), anyBoolean(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 84300da..cdef964 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -137,11 +137,22 @@
                 Mockito.eq(userHandle))).thenReturn(
                 mMediaControllers);
 
-        mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
-                mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotifCollection, mDialogTransitionAnimator,
-                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
-                mKeyguardManager, mFlags, mUserTracker);
+        mMediaOutputController =
+                new MediaOutputController(
+                        mContext,
+                        TEST_PACKAGE,
+                        mContext.getUser(),
+                        mMediaSessionManager,
+                        mLocalBluetoothManager,
+                        mStarter,
+                        mNotifCollection,
+                        mDialogTransitionAnimator,
+                        mNearbyMediaDevicesManager,
+                        mAudioManager,
+                        mPowerExemptionManager,
+                        mKeyguardManager,
+                        mFlags,
+                        mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = makeTestDialog(mMediaOutputController);
         mMediaOutputDialog.show();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index a702dda..224e755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.res.Configuration;
 import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -55,6 +56,8 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Lazy;
@@ -117,6 +120,7 @@
     EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
     @Mock
     NotificationShadeWindowController mNotificationShadeWindowController;
+    ConfigurationController mConfigurationController = new FakeConfigurationController();
 
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
@@ -144,9 +148,8 @@
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
-                mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue,
-                mSynchronousExecutor);
-
+                mDisplayTracker, mNotificationShadeWindowController, mConfigurationController,
+                mDumpManager, mCommandQueue, mSynchronousExecutor);
     }
 
     @Test
@@ -335,6 +338,12 @@
         assertThat(state2.mWindowState).isNotEqualTo(newState);
     }
 
+    @Test
+    public void configUpdatePropagatesToEdgeBackGestureHandler() {
+        mConfigurationController.onConfigurationChanged(Configuration.EMPTY);
+        verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any());
+    }
+
     private List<String> createFakeShortcutTargets() {
         return new ArrayList<>(List.of("a", "b", "c", "d"));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index d405df7..354a87a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -37,11 +37,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 import android.util.SparseArray;
 
@@ -293,23 +290,6 @@
     }
 
     @Test
-    public void testConfigurationChange_taskbarNotInitialized() {
-        Configuration configuration = mContext.getResources().getConfiguration();
-        mNavigationBarController.mIsLargeScreen = true;
-        mNavigationBarController.onConfigChanged(configuration);
-        verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
-    }
-
-    @Test
-    public void testConfigurationChange_taskbarInitialized() {
-        Configuration configuration = mContext.getResources().getConfiguration();
-        mNavigationBarController.mIsLargeScreen = true;
-        when(mTaskbarDelegate.isInitialized()).thenReturn(true);
-        mNavigationBarController.onConfigChanged(configuration);
-        verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
-    }
-
-    @Test
     public void testShouldRenderTaskbar_taskbarNotRenderedOnPhone() {
         mNavigationBarController.mIsLargeScreen = false;
         mNavigationBarController.mIsPhone = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index b38d5e3..0e7a215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -111,6 +111,7 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.DeviceConfigProxyFake;
@@ -275,8 +276,8 @@
                     mKeyguardStateController, mock(NavigationModeController.class),
                     mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
                     mock(UserTracker.class), mock(DisplayTracker.class),
-                    mNotificationShadeWindowController, mock(DumpManager.class),
-                    mock(CommandQueue.class), mSynchronousExecutor));
+                    mNotificationShadeWindowController, mock(ConfigurationController.class),
+                    mock(DumpManager.class), mock(CommandQueue.class), mSynchronousExecutor));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
         });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index e4a4836..6956bea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -57,6 +57,7 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
@@ -66,7 +67,6 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
@@ -115,7 +115,6 @@
     @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private FeatureFlagsClassic mFeatureFlags;
-    @Mock private SceneContainerFlags mSceneContainerFlags;
     private ViewGroup mQsView;
 
     private final CommandQueue mCommandQueue =
@@ -127,7 +126,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mSceneContainerFlags.isEnabled()).thenReturn(false);
 
         mUnderTest = instantiate();
 
@@ -496,8 +494,8 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
-        when(mSceneContainerFlags.isEnabled()).thenReturn(true);
         clearInvocations(
                 mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
         QSImpl other = instantiate();
@@ -513,9 +511,8 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
-        when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
         mUnderTest.onStateChanged(KEYGUARD);
         assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
 
@@ -524,9 +521,8 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
-        when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
         mUnderTest.onStateChanged(KEYGUARD);
         assertThat(mUnderTest.isKeyguardState()).isFalse();
 
@@ -559,8 +555,8 @@
                 mFooterActionsViewModelFactory,
                 mFooterActionsViewBinder,
                 mLargeScreenShadeInterpolator,
-                mFeatureFlags,
-                mSceneContainerFlags);
+                mFeatureFlags
+        );
     }
 
     private void setUpOther() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index a60494f..0275643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -17,13 +17,13 @@
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.settings.brightness.BrightnessController
 import com.android.systemui.settings.brightness.BrightnessSliderController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.tuner.TunerService
 import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -36,7 +36,6 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import javax.inject.Provider
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
@@ -65,8 +64,6 @@
     @Mock private lateinit var pagedTileLayout: PagedTileLayout
     @Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
 
-    private val sceneContainerFlags = FakeSceneContainerFlags()
-
     private lateinit var controller: QSPanelController
     private val testableResources: TestableResources = mContext.orCreateTestableResources
 
@@ -103,7 +100,6 @@
             falsingManager,
             statusBarKeyguardViewManager,
             ResourcesSplitShadeStateController(),
-            sceneContainerFlags,
             longPressEffectProvider,
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 8acde36..4915e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -20,15 +20,14 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Answers
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -43,8 +42,6 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var context: Context
 
-    private val sceneContainerFlags = FakeSceneContainerFlags()
-
     private lateinit var controller: QuickStatusBarHeaderController
 
     @Before
@@ -55,9 +52,8 @@
         `when`(view.context).thenReturn(context)
 
         controller = QuickStatusBarHeaderController(
-                view,
-                quickQSPanelController,
-                sceneContainerFlags,
+            view,
+            quickQSPanelController,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 1313227..387f27d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -42,7 +42,6 @@
 import com.android.systemui.navigationbar.NavigationBarController
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeViewController
@@ -249,7 +248,6 @@
             sysuiUnlockAnimationController,
             inWindowLauncherUnlockAnimationManager,
             assistUtils,
-            FakeSceneContainerFlags(),
             dumpManager,
             unfoldTransitionProgressForwarder,
             broadcastDispatcher
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index cf7c6f4..b89ccef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -75,8 +75,6 @@
 import com.android.systemui.scene.FakeWindowRootViewComponent;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -136,7 +134,6 @@
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private UserTracker mUserTracker;
-    @Mock private SceneContainerFlags mSceneContainerFlags;
     @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
@@ -185,14 +182,12 @@
                 mKosmos.getDeviceUnlockedInteractor());
 
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
-        FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
         KeyguardTransitionInteractor keyguardTransitionInteractor =
                 mKosmos.getKeyguardTransitionInteractor();
         KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
                 keyguardRepository,
                 new FakeCommandQueue(),
                 powerInteractor,
-                sceneContainerFlags,
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(configurationRepository),
                 shadeRepository,
@@ -256,7 +251,6 @@
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
                 mUserTracker,
-                mSceneContainerFlags,
                 () -> communalInteractor) {
                     @Override
                     protected boolean isDebuggable() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 20d877e..77ad17a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -62,7 +62,6 @@
 import com.android.systemui.res.R;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -208,14 +207,12 @@
                 mock(SceneLogger.class),
                 mKosmos.getDeviceUnlockedInteractor());
 
-        FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
         KeyguardTransitionInteractor keyguardTransitionInteractor =
                 mKosmos.getKeyguardTransitionInteractor();
         KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
                 mKeyguardRepository,
                 new FakeCommandQueue(),
                 powerInteractor,
-                sceneContainerFlags,
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(configurationRepository),
                 mShadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 433c95a..6bdd3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -95,7 +94,6 @@
             headsUpManager,
             PowerInteractorFactory.create().powerInteractor,
             ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
-            kosmos.sceneContainerFlags,
             kosmos::sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index 56fc0c7..a05a23b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -34,21 +34,19 @@
 @RunWith(AndroidTestingRunner::class)
 class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
 
-    private val progressProvider = TestUnfoldTransitionProvider()
+    private val progressProvider = FakeUnfoldTransitionProvider()
 
-    @Mock
-    private lateinit var parent: ViewGroup
+    @Mock private lateinit var parent: ViewGroup
 
-    @Mock
-    private lateinit var shouldBeAnimated: () -> Boolean
+    @Mock private lateinit var shouldBeAnimated: () -> Boolean
 
     private lateinit var animator: UnfoldConstantTranslateAnimator
 
     private val viewsIdToRegister
         get() =
             setOf(
-                    ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated),
-                    ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated)
+                ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated),
+                ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated)
             )
 
     @Before
@@ -122,11 +120,12 @@
         progressProvider.onTransitionStarted()
         progressProvider.onTransitionProgress(0f)
 
-        val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
-            1
-        } else {
-            -1
-        }
+        val rtlMultiplier =
+            if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+                1
+            } else {
+                -1
+            }
         list.forEach { (view, direction) ->
             assertEquals(
                 (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d2fc087..be5af88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -55,7 +55,6 @@
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -87,8 +86,8 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -135,7 +134,6 @@
         val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val featureFlags = FakeFeatureFlagsClassic()
         val shadeRepository = FakeShadeRepository()
-        val sceneContainerFlags = FakeSceneContainerFlags()
         val configurationRepository = FakeConfigurationRepository()
         val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
         fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
@@ -146,7 +144,6 @@
                 keyguardRepository,
                 FakeCommandQueue(),
                 powerInteractor,
-                sceneContainerFlags,
                 FakeKeyguardBouncerRepository(),
                 ConfigurationInteractor(configurationRepository),
                 shadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 54a6523..bb68ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -138,7 +138,6 @@
                 mHeadsUpManager,
                 mPowerInteractor,
                 mActiveNotificationsInteractor,
-                mKosmos.getSceneContainerFlags(),
                 () -> mKosmos.getSceneInteractor());
         mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index db053d8..9e2856d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -180,7 +180,6 @@
                 mHeadsUpManager,
                 PowerInteractorFactory.create().getPowerInteractor(),
                 mActiveNotificationsInteractor,
-                mKosmos.getSceneContainerFlags(),
                 () -> mKosmos.getSceneInteractor()
         );
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 65a960b..1b85dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -45,6 +45,7 @@
 import com.android.internal.statusbar.statusBarService
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -55,7 +56,6 @@
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.shade.shadeControllerSceneImpl
@@ -93,6 +93,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
+@EnableSceneContainer
 class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
     private val testNotificationChannel =
         NotificationChannel(
@@ -143,8 +144,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        val sceneContainerFlags = kosmos.fakeSceneContainerFlags
-        sceneContainerFlags.enabled = true
         allowTestableLooperAsMainThread()
         helper = NotificationTestHelper(mContext, mDependency)
         Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
@@ -156,9 +155,9 @@
                 headsUpManager,
                 create().powerInteractor,
                 activeNotificationsInteractor,
-                sceneContainerFlags,
-                { sceneInteractor },
-            )
+            ) {
+                sceneInteractor
+            }
         gutsManager =
             NotificationGutsManager(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 4bfd7e3..df82df8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
 import com.android.systemui.power.shared.model.WakefulnessState.STARTING_TO_SLEEP
 import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
 import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository
@@ -59,7 +59,7 @@
 
     private val animationStatus = FakeAnimationStatusRepository()
     private val configurationController = FakeConfigurationController()
-    private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+    private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
     private val powerRepository = FakePowerRepository()
     private val powerInteractor =
         PowerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 25e4728..5c65103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -101,6 +102,7 @@
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentService;
@@ -120,7 +122,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
@@ -336,8 +337,6 @@
     private final DumpManager mDumpManager = new DumpManager();
     private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
 
-    private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
-
     private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
             mKosmos.getBrightnessMirrorShowingInteractor();
 
@@ -351,7 +350,9 @@
         // Turn AOD on and toggle feature flag for jank fixes
         mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+        if (!com.android.systemui.Flags.sceneContainer()) {
+            mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+        }
 
         IThermalService thermalService = mock(IThermalService.class);
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -426,22 +427,25 @@
             ((Runnable) invocation.getArgument(0)).run();
             return null;
         }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
-
-        mShadeController = spy(new ShadeControllerImpl(
-                mCommandQueue,
-                mMainExecutor,
-                mock(WindowRootViewVisibilityInteractor.class),
-                mKeyguardStateController,
-                mStatusBarStateController,
-                mStatusBarKeyguardViewManager,
-                mStatusBarWindowController,
-                mDeviceProvisionedController,
-                mNotificationShadeWindowController,
-                0,
-                () -> mNotificationPanelViewController,
-                () -> mAssistManager,
-                () -> mNotificationGutsManager
-        ));
+        if (com.android.systemui.Flags.sceneContainer()) {
+            mShadeController = spy(mKosmos.getShadeController());
+        } else {
+            mShadeController = spy(new ShadeControllerImpl(
+                    mCommandQueue,
+                    mMainExecutor,
+                    mock(WindowRootViewVisibilityInteractor.class),
+                    mKeyguardStateController,
+                    mStatusBarStateController,
+                    mStatusBarKeyguardViewManager,
+                    mStatusBarWindowController,
+                    mDeviceProvisionedController,
+                    mNotificationShadeWindowController,
+                    0,
+                    () -> mNotificationPanelViewController,
+                    () -> mAssistManager,
+                    () -> mNotificationGutsManager
+            ));
+        }
         mShadeController.setNotificationShadeWindowViewController(
                 mNotificationShadeWindowViewController);
         mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -562,7 +566,6 @@
                 mUserTracker,
                 () -> mFingerprintManager,
                 mActivityStarter,
-                mSceneContainerFlags,
                 mBrightnessMirrorShowingInteractor
         );
         mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
@@ -572,8 +575,7 @@
                 any(NotificationPanelViewController.class),
                 any(ShadeExpansionStateManager.class),
                 any(BiometricUnlockController.class),
-                any(ViewGroup.class),
-                any(KeyguardBypassController.class)))
+                any(ViewGroup.class)))
                 .thenReturn(mStatusBarKeyguardViewManager);
 
         when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
@@ -1095,25 +1097,24 @@
     }
 
     @Test
+    @EnableSceneContainer
     public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
-        mSceneContainerFlags.setEnabled(true);
         mCentralSurfaces.registerCallbacks();
 
         mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
         mTestScope.getTestScheduler().runCurrent();
-        verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+        verify(mScrimController, atLeastOnce()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
 
         mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
         mTestScope.getTestScheduler().runCurrent();
         ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
         // The default is to call the one with the callback argument
-        verify(mScrimController).transitionTo(captor.capture(), any());
+        verify(mScrimController, atLeastOnce()).transitionTo(captor.capture(), any());
         assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
     }
 
     @Test
     public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
-        mSceneContainerFlags.setEnabled(false);
         mCentralSurfaces.registerCallbacks();
 
         mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 7362e34..6150253 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -72,6 +74,7 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var devicePostureController: DevicePostureController
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var packageManager: PackageManager
@@ -138,7 +141,8 @@
     private fun initKeyguardBypassController() {
         keyguardBypassController =
             KeyguardBypassController(
-                context,
+                context.resources,
+                context.packageManager,
                 testScope.backgroundScope,
                 tunerService,
                 statusBarStateController,
@@ -146,7 +150,8 @@
                 keyguardStateController,
                 shadeRepository,
                 devicePostureController,
-                dumpManager
+                keyguardTransitionInteractor,
+                dumpManager,
             )
     }
 
@@ -302,4 +307,26 @@
             job.cancel()
         }
     }
+
+    @Test
+    fun canBypass_bypassDisabled() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_unlock_bypass_override,
+            2 /* FACE_UNLOCK_BYPASS_NEVER */
+        )
+        initKeyguardBypassController()
+        assertThat(keyguardBypassController.canBypass()).isFalse()
+    }
+
+    @Test
+    fun canBypass_bypassEnabled_alternateBouncerShowing() {
+        context.orCreateTestableResources.addOverride(
+            R.integer.config_face_unlock_bypass_override,
+            1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+        )
+        initKeyguardBypassController()
+        whenever(keyguardTransitionInteractor.getCurrentState())
+            .thenReturn(KeyguardState.ALTERNATE_BOUNCER)
+        assertThat(keyguardBypassController.canBypass()).isTrue()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index dc3db4c..a6a4f24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -20,9 +20,9 @@
 import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
 import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
 
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -172,7 +172,6 @@
                 mKeyguardRepository,
                 mCommandQueue,
                 PowerInteractorFactory.create().getPowerInteractor(),
-                mKosmos.getSceneContainerFlags(),
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(new FakeConfigurationRepository()),
                 new FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 1463680..d365663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
@@ -51,6 +50,8 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import javax.inject.Provider
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
@@ -61,8 +62,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
-import javax.inject.Provider
 
 @SmallTest
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@@ -296,7 +295,6 @@
             Optional.of(sysuiUnfoldComponent),
             Optional.of(progressProvider),
             featureFlags,
-            FakeSceneContainerFlags(),
             userChipViewModel,
             centralSurfacesImpl,
             statusBarWindowStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index f8c01e7..f04a5ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -241,8 +241,7 @@
                 mShadeLockscreenInteractor,
                 new ShadeExpansionStateManager(),
                 mBiometricUnlockController,
-                mNotificationContainer,
-                mBypassController);
+                mNotificationContainer);
         mStatusBarKeyguardViewManager.show(null);
         ArgumentCaptor<PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
                 ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index feff046..1ec1765 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -8,7 +8,7 @@
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import com.android.systemui.util.mockito.whenever
@@ -23,17 +23,14 @@
 @TestableLooper.RunWithLooper
 class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var windowManager: WindowManager
+    @Mock private lateinit var windowManager: WindowManager
 
-    @Mock
-    private lateinit var display: Display
+    @Mock private lateinit var display: Display
 
-    @Mock
-    private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
+    @Mock private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
 
     private val view: View = View(context)
-    private val progressProvider = TestUnfoldTransitionProvider()
+    private val progressProvider = FakeUnfoldTransitionProvider()
     private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
 
     private lateinit var controller: StatusBarMoveFromCenterAnimationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index 1e628bd..dedd0af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -13,68 +13,103 @@
  */
 package com.android.systemui.statusbar.phone
 
+import android.app.Dialog
 import android.content.res.Configuration
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.view.WindowManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.Mockito.verify
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 class SystemUIBottomSheetDialogTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
     private val configurationController = mock<ConfigurationController>()
     private val config = mock<Configuration>()
+    private val delegate = mock<DialogDelegate<Dialog>>()
 
     private lateinit var dialog: SystemUIBottomSheetDialog
 
     @Before
     fun setup() {
-        dialog = SystemUIBottomSheetDialog(mContext, configurationController)
+        dialog =
+            with(kosmos) {
+                SystemUIBottomSheetDialog(
+                    context,
+                    testScope.backgroundScope,
+                    configurationController,
+                    delegate,
+                    TestLayout(),
+                    0,
+                )
+            }
     }
 
     @Test
     fun onStart_registersConfigCallback() {
-        dialog.show()
+        kosmos.testScope.runTest {
+            dialog.show()
+            runCurrent()
 
-        verify(configurationController).addCallback(any())
+            verify(configurationController).addCallback(any())
+        }
     }
 
     @Test
     fun onStop_unregisterConfigCallback() {
-        dialog.show()
-        dialog.dismiss()
+        kosmos.testScope.runTest {
+            dialog.show()
+            runCurrent()
+            dialog.dismiss()
+            runCurrent()
 
-        verify(configurationController).removeCallback(any())
+            verify(configurationController).removeCallback(any())
+        }
     }
 
     @Test
-    fun onConfigurationChanged_calledInSubclass() {
-        var onConfigChangedCalled = false
-        val subclass =
-            object : SystemUIBottomSheetDialog(mContext, configurationController) {
-                override fun onConfigurationChanged() {
-                    onConfigChangedCalled = true
-                }
-            }
+    fun onConfigurationChanged_calledInDelegate() {
+        kosmos.testScope.runTest {
+            dialog.show()
+            runCurrent()
+            val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+            verify(configurationController).addCallback(capture(captor))
 
-        subclass.show()
+            captor.value.onConfigChanged(config)
+            runCurrent()
 
-        val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
-        verify(configurationController).addCallback(capture(captor))
-        captor.value.onConfigChanged(config)
+            verify(delegate).onConfigurationChanged(any(), any())
+        }
+    }
 
-        assertThat(onConfigChangedCalled).isTrue()
+    private class TestLayout : SystemUIBottomSheetDialog.WindowLayout {
+        override fun calculate(): Flow<SystemUIBottomSheetDialog.WindowLayout.Layout> {
+            return flowOf(
+                SystemUIBottomSheetDialog.WindowLayout.Layout(
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                )
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 9b6940e..598b12c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -137,6 +137,7 @@
                 wifiRepository,
                 mock(),
                 mock(),
+                mock(),
             )
 
         demoRepo =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 07abd27..b7a3b30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -80,6 +80,7 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
@@ -229,6 +230,7 @@
                 wifiRepository,
                 fullConnectionFactory,
                 updateMonitor,
+                mock(),
             )
 
         testScope.runCurrent()
@@ -529,6 +531,7 @@
         }
 
     @Test
+    @Ignore("b/333912012")
     fun testConnectionCache_clearsInvalidSubscriptions() =
         testScope.runTest {
             collectLastValue(underTest.subscriptions)
@@ -553,6 +556,7 @@
         }
 
     @Test
+    @Ignore("b/333912012")
     fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
         testScope.runTest {
             collectLastValue(underTest.subscriptions)
@@ -581,6 +585,7 @@
 
     /** Regression test for b/261706421 */
     @Test
+    @Ignore("b/333912012")
     fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
         testScope.runTest {
             collectLastValue(underTest.subscriptions)
@@ -604,6 +609,54 @@
         }
 
     @Test
+    fun testConnectionsCache_keepsReposCached() =
+        testScope.runTest {
+            // Collect subscriptions to start the job
+            collectLastValue(underTest.subscriptions)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
+
+            // All subscriptions disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Sub1 comes back
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
+
+            assertThat(repo1_1).isSameInstanceAs(repo1_2)
+        }
+
+    @Test
+    fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
+        testScope.runTest {
+            // Collect subscriptions to start the job
+            collectLastValue(underTest.subscriptions)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Client grabs a reference to a repository, but doesn't keep it around
+            underTest.getRepoForSubId(SUB_1_ID)
+
+            // All subscriptions disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+
+            assertThat(repo1).isNotNull()
+        }
+
+    @Test
     fun testConnectionRepository_invalidSubId_doesNotThrow() =
         testScope.runTest {
             underTest.getRepoForSubId(SUB_1_ID)
@@ -1063,7 +1116,8 @@
                     airplaneModeRepository,
                     wifiRepository,
                     fullConnectionFactory,
-                    updateMonitor
+                    updateMonitor,
+                    mock(),
                 )
 
             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index f9ab25e..dfe8023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -43,15 +43,12 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.DurationUnit
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -694,32 +691,6 @@
             assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
         }
 
-    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
-    @Test
-    fun satBasedIcon_hasHysteresisWhenDisabled() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.signalLevelIcon)
-
-            val hysteresisDuration = 5.seconds
-            connectionRepository.satelliteConnectionHysteresisSeconds.value =
-                hysteresisDuration.toInt(DurationUnit.SECONDS)
-
-            connectionRepository.isNonTerrestrial.value = true
-
-            assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
-            // Disable satellite
-            connectionRepository.isNonTerrestrial.value = false
-
-            // Satellite icon should still be visible
-            assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
-            // Wait for the icon to change
-            advanceTimeBy(hysteresisDuration)
-
-            assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
-        }
-
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index 42bbe3e..c4ab943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -26,6 +26,10 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import com.android.systemui.util.mockito.mock
@@ -53,6 +57,10 @@
     private val deviceProvisionedRepository = FakeDeviceProvisioningRepository()
     private val deviceProvisioningInteractor =
         DeviceProvisioningInteractor(deviceProvisionedRepository)
+    private val connectivityRepository = FakeConnectivityRepository()
+    private val wifiRepository = FakeWifiRepository()
+    private val wifiInteractor =
+        WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
 
     @Before
     fun setUp() {
@@ -61,6 +69,7 @@
                 repo,
                 iconsInteractor,
                 deviceProvisioningInteractor,
+                wifiInteractor,
                 testScope.backgroundScope,
             )
     }
@@ -103,6 +112,7 @@
                     repo,
                     iconsInteractor,
                     deviceProvisioningInteractor,
+                    wifiInteractor,
                     testScope.backgroundScope,
                 )
 
@@ -150,6 +160,7 @@
                     repo,
                     iconsInteractor,
                     deviceProvisioningInteractor,
+                    wifiInteractor,
                     testScope.backgroundScope,
                 )
 
@@ -205,6 +216,7 @@
                     repo,
                     iconsInteractor,
                     deviceProvisioningInteractor,
+                    wifiInteractor,
                     testScope.backgroundScope,
                 )
 
@@ -337,6 +349,7 @@
                     repo,
                     iconsInteractor,
                     deviceProvisioningInteractor,
+                    wifiInteractor,
                     testScope.backgroundScope,
                 )
 
@@ -353,4 +366,28 @@
             // THEN the value is still false, because the flag is off
             assertThat(latest).isFalse()
         }
+
+    @Test
+    fun isWifiActive_falseWhenWifiNotActive() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiActive)
+
+            // WHEN wifi is not active
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+            // THEN the interactor returns false due to the wifi network not being active
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiActive_trueWhenWifiIsActive() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiActive)
+
+            // WHEN wifi is active
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+            // THEN the interactor returns true due to the wifi network being active
+            assertThat(latest).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 1d6cd37..64f19b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -26,6 +26,10 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import com.android.systemui.util.mockito.mock
@@ -44,14 +48,18 @@
     private lateinit var underTest: DeviceBasedSatelliteViewModel
     private lateinit var interactor: DeviceBasedSatelliteInteractor
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
-
     private val repo = FakeDeviceBasedSatelliteRepository()
+    private val testScope = TestScope()
+
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
     private val deviceProvisionedRepository = FakeDeviceProvisioningRepository()
     private val deviceProvisioningInteractor =
         DeviceProvisioningInteractor(deviceProvisionedRepository)
-
-    private val testScope = TestScope()
+    private val connectivityRepository = FakeConnectivityRepository()
+    private val wifiRepository = FakeWifiRepository()
+    private val wifiInteractor =
+        WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
 
     @Before
     fun setUp() {
@@ -63,6 +71,7 @@
                 repo,
                 mobileIconsInteractor,
                 deviceProvisioningInteractor,
+                wifiInteractor,
                 testScope.backgroundScope,
             )
 
@@ -253,4 +262,40 @@
             // THEN icon is null because the device is not provisioned
             assertThat(latest).isInstanceOf(Icon::class.java)
         }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun icon_wifiIsActive() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // GIVEN device is provisioned
+            deviceProvisionedRepository.setDeviceProvisioned(true)
+
+            // GIVEN wifi network is active
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+            // THEN icon is null because the device is connected to wifi
+            assertThat(latest).isNull()
+
+            // GIVEN device loses wifi connection
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+            // Wait for delay to be completed
+            advanceTimeBy(10.seconds)
+
+            // THEN icon is set because the device lost wifi connection
+            assertThat(latest).isInstanceOf(Icon::class.java)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 69536c5..bdd3d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
@@ -60,7 +59,6 @@
             keyguardRepository,
             mock<CommandQueue>(),
             PowerInteractorFactory.create().powerInteractor,
-            kosmos.sceneContainerFlags,
             FakeKeyguardBouncerRepository(),
             ConfigurationInteractor(FakeConfigurationRepository()),
             FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 28adbce..383f4a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -86,7 +86,7 @@
     private val areAnimationEnabled = MutableStateFlow(true)
     private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
     private val systemClock = FakeSystemClock()
-    private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+    private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
     private val unfoldTransitionRepository =
         UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
     private val unfoldTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index b9c7e61..fd513c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -35,7 +35,7 @@
 @SmallTest
 class UnfoldHapticsPlayerTest : SysuiTestCase() {
 
-    private val progressProvider = TestUnfoldTransitionProvider()
+    private val progressProvider = FakeUnfoldTransitionProvider()
     private val vibrator: Vibrator = mock()
     private val testFoldProvider = TestFoldProvider()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index ba72716..2955384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.unfold.util.FoldableDeviceStates
 import com.android.systemui.unfold.util.FoldableTestUtils
 import com.android.systemui.util.mockito.any
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,45 +38,41 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class UnfoldLatencyTrackerTest : SysuiTestCase() {
 
-    @Mock
-    lateinit var latencyTracker: LatencyTracker
+    @Mock lateinit var latencyTracker: LatencyTracker
 
-    @Mock
-    lateinit var deviceStateManager: DeviceStateManager
+    @Mock lateinit var deviceStateManager: DeviceStateManager
 
-    @Mock
-    lateinit var screenLifecycle: ScreenLifecycle
+    @Mock lateinit var screenLifecycle: ScreenLifecycle
 
-    @Captor
-    private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+    @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
 
-    @Captor
-    private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
+    @Captor private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
 
     private lateinit var deviceStates: FoldableDeviceStates
 
     private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
 
-    private val transitionProgressProvider = TestUnfoldTransitionProvider()
+    private val transitionProgressProvider = FakeUnfoldTransitionProvider()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        unfoldLatencyTracker = UnfoldLatencyTracker(
-            latencyTracker,
-            deviceStateManager,
-            Optional.of(transitionProgressProvider),
-            context.mainExecutor,
-            context,
-            context.contentResolver,
-            screenLifecycle
-        ).apply { init() }
+        unfoldLatencyTracker =
+            UnfoldLatencyTracker(
+                    latencyTracker,
+                    deviceStateManager,
+                    Optional.of(transitionProgressProvider),
+                    context.mainExecutor,
+                    context,
+                    context.contentResolver,
+                    screenLifecycle
+                )
+                .apply { init() }
         deviceStates = FoldableTestUtils.findDeviceStates(context)
 
         verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
@@ -107,7 +104,7 @@
     }
 
     @Test
-    fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+    fun firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
         setAnimationsEnabled(true)
         sendFoldEvent(folded = false)
 
@@ -118,7 +115,7 @@
     }
 
     @Test
-    fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+    fun secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
         setAnimationsEnabled(true)
         sendFoldEvent(folded = true)
         sendFoldEvent(folded = false)
@@ -131,7 +128,7 @@
     }
 
     @Test
-    fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+    fun unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
         setAnimationsEnabled(true)
         sendFoldEvent(folded = false)
         sendFoldEvent(folded = true)
@@ -196,4 +193,4 @@
             durationScale.toString()
         )
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
index 6ec0251..0c452eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
@@ -17,21 +17,18 @@
 @SmallTest
 class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var wallpaperController: WallpaperController
+    @Mock private lateinit var wallpaperController: WallpaperController
 
-    private val progressProvider = TestUnfoldTransitionProvider()
+    private val progressProvider = FakeUnfoldTransitionProvider()
 
-    @JvmField
-    @Rule
-    val mockitoRule = MockitoJUnit.rule()
+    @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
     private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController
 
     @Before
     fun setup() {
-        unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider,
-            wallpaperController)
+        unfoldWallpaperController =
+            UnfoldTransitionWallpaperController(progressProvider, wallpaperController)
         unfoldWallpaperController.init()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
index 2bc05fc..e5f619b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -23,7 +23,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.utils.os.FakeHandler
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
@@ -34,7 +34,7 @@
 @RunWithLooper(setAsMainLooper = true)
 class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() {
 
-    private val wrappedProgressProvider = TestUnfoldTransitionProvider()
+    private val wrappedProgressProvider = FakeUnfoldTransitionProvider()
     private val fakeHandler = FakeHandler(Looper.getMainLooper())
     private val listener = TestUnfoldProgressListener()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index d864d53..70ec050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -20,7 +20,7 @@
 import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
@@ -43,14 +43,14 @@
 
     @Mock lateinit var rotationChangeProvider: RotationChangeProvider
 
-    private val sourceProvider = TestUnfoldTransitionProvider()
+    private val sourceProvider = FakeUnfoldTransitionProvider()
 
     @Mock lateinit var transitionListener: TransitionProgressListener
 
     @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
 
     lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
-    private lateinit var testableLooper : TestableLooper
+    private lateinit var testableLooper: TestableLooper
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index 2f29b3b..451bd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.util.mockito.any
 import org.junit.Before
@@ -42,7 +42,7 @@
 
     @Mock lateinit var sinkProvider: TransitionProgressListener
 
-    private val sourceProvider = TestUnfoldTransitionProvider()
+    private val sourceProvider = FakeUnfoldTransitionProvider()
 
     private lateinit var contentResolver: ContentResolver
     private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
@@ -132,6 +132,6 @@
             durationScale.toString()
         )
 
-        animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+        animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */ false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 95c934e..4486402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -23,7 +23,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.unfold.progress.TestUnfoldProgressListener
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
@@ -43,7 +43,7 @@
 @RunWithLooper
 class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
 
-    private val rootProvider = TestUnfoldTransitionProvider()
+    private val rootProvider = FakeUnfoldTransitionProvider()
     private val listener = TestUnfoldProgressListener()
     private val testScope = TestScope(UnconfinedTestDispatcher())
     private val bgThread =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
index f484ea0..cd4d7b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
@@ -19,7 +19,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
 import com.android.systemui.unfold.progress.TestUnfoldProgressListener
 import com.google.common.util.concurrent.MoreExecutors
 import org.junit.Before
@@ -32,7 +32,7 @@
 class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
 
     private val listener = TestUnfoldProgressListener()
-    private val sourceProvider = TestUnfoldTransitionProvider()
+    private val sourceProvider = FakeUnfoldTransitionProvider()
 
     private val foldProvider = TestFoldProvider()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
index fb82b8f..483dc0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
@@ -15,14 +15,14 @@
  */
 package com.android.systemui.volume;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.media.MediaMetadata;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 
-import junit.framework.Assert;
-
 import org.junit.Test;
 
 @SmallTest
@@ -30,11 +30,59 @@
 
     @Test
     public void testMediaMetadataToString_null() {
-        Assert.assertEquals(null, Util.mediaMetadataToString(null));
+        assertThat(Util.mediaMetadataToString(null)).isNull();
     }
 
     @Test
     public void testMediaMetadataToString_notNull() {
-        Assert.assertNotNull(Util.mediaMetadataToString(new MediaMetadata.Builder().build()));
+        assertThat(Util.mediaMetadataToString(new MediaMetadata.Builder().build())).isNotNull();
+    }
+
+    @Test
+    public void translateToRange_translatesStartToStart() {
+        assertThat(
+                (int) Util.translateToRange(
+                        /* value= */ 0,
+                        /* valueRangeStart= */ 0,
+                        /* valueRangeEnd= */ 7,
+                        /* targetRangeStart= */ 0,
+                        /* targetRangeEnd= */700)
+        ).isEqualTo(0);
+    }
+
+    @Test
+    public void translateToRange_translatesValueToValue() {
+        assertThat(
+                (int) Util.translateToRange(
+                        /* value= */ 4,
+                        /* valueRangeStart= */ 0,
+                        /* valueRangeEnd= */ 7,
+                        /* targetRangeStart= */ 0,
+                        /* targetRangeEnd= */700)
+        ).isEqualTo(400);
+    }
+
+    @Test
+    public void translateToRange_translatesEndToEnd() {
+        assertThat(
+                (int) Util.translateToRange(
+                        /* value= */ 7,
+                        /* valueRangeStart= */ 0,
+                        /* valueRangeEnd= */ 7,
+                        /* targetRangeStart= */ 0,
+                        /* targetRangeEnd= */700)
+        ).isEqualTo(700);
+    }
+
+    @Test
+    public void translateToRange_returnsStartForEmptyRange() {
+        assertThat(
+                (int) Util.translateToRange(
+                        /* value= */ 7,
+                        /* valueRangeStart= */ 7,
+                        /* valueRangeEnd= */ 7,
+                        /* targetRangeStart= */ 700,
+                        /* targetRangeEnd= */700)
+        ).isEqualTo(700);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index ed2fb2c..3b468aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -248,6 +248,8 @@
             VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
             ss.name = STREAMS.get(i);
             ss.level = 1;
+            ss.levelMin = 0;
+            ss.levelMax = 25;
             state.states.append(i, ss);
         }
         return state;
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 c24c86c..d9a0c4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -121,8 +121,6 @@
 import com.android.systemui.scene.FakeWindowRootViewComponent;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -360,8 +358,6 @@
     @Mock
     private Display mDefaultDisplay;
     @Mock
-    private SceneContainerFlags mSceneContainerFlags;
-    @Mock
     private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
 
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -430,14 +426,12 @@
                 mock(SceneLogger.class),
                 mKosmos.getDeviceUnlockedInteractor());
 
-        FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
         KeyguardTransitionInteractor keyguardTransitionInteractor =
                 mKosmos.getKeyguardTransitionInteractor();
         KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
                 keyguardRepository,
                 new FakeCommandQueue(),
                 powerInteractor,
-                sceneContainerFlags,
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(configurationRepository),
                 shadeRepository,
@@ -503,7 +497,6 @@
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
                 mUserTracker,
-                mSceneContainerFlags,
                 mKosmos::getCommunalInteractor
         );
         mNotificationShadeWindowController.fetchWindowRootView();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index de7b14d..0682361 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
 import com.android.systemui.scene.SceneContainerFrameworkModule
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSource
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -104,11 +104,10 @@
 
         @Provides
         fun provideBaseShadeInteractor(
-            sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
             sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
         ): BaseShadeInteractor {
-            return if (sceneContainerFlags.isEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
             } else {
                 sceneContainerOff.get()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
new file mode 100644
index 0000000..8b0affe2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility
+
+import com.android.systemui.qs.ReduceBrightColorsController
+
+class FakeReduceBrightColorsController : ReduceBrightColorsController {
+
+    private var isEnabled = false
+
+    private val callbacks = LinkedHashSet<ReduceBrightColorsController.Listener>()
+
+    override fun addCallback(listener: ReduceBrightColorsController.Listener) {
+        callbacks.add(listener)
+    }
+
+    override fun removeCallback(listener: ReduceBrightColorsController.Listener) {
+        callbacks.remove(listener)
+    }
+
+    override fun isReduceBrightColorsActivated(): Boolean {
+        return isEnabled
+    }
+
+    override fun setReduceBrightColorsActivated(activated: Boolean) {
+        if (activated != isEnabled) {
+            isEnabled = activated
+            for (callback in callbacks) {
+                callback.onActivated(activated)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
index 979d8e7..5bbe3bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.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.
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.accessibility
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.ReduceBrightColorsController
 
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+var Kosmos.reduceBrightColorsController: ReduceBrightColorsController by
+    Kosmos.Fixture { fakeReduceBrightColorsController }
+val Kosmos.fakeReduceBrightColorsController by Kosmos.Fixture { FakeReduceBrightColorsController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index a6dd3cd..219794f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -32,6 +32,8 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.currentTime
 
@@ -68,6 +70,8 @@
 
     var lockoutStartedReportCount = 0
 
+    private val credentialCheckingMutex = Mutex(locked = false)
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return authenticationMethod.value
     }
@@ -124,30 +128,32 @@
     override suspend fun checkCredential(
         credential: LockscreenCredential
     ): AuthenticationResultModel {
-        val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
-        val isSuccessful =
-            when {
-                credential.type != getCurrentCredentialType(securityMode) -> false
-                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
-                    credential.isPin && credential.matches(expectedCredential)
-                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
-                    credential.isPassword && credential.matches(expectedCredential)
-                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
-                    credential.isPattern && credential.matches(expectedCredential)
-                else -> error("Unexpected credential type ${credential.type}!")
-            }
+        return credentialCheckingMutex.withLock {
+            val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+            val isSuccessful =
+                when {
+                    credential.type != getCurrentCredentialType(securityMode) -> false
+                    credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+                        credential.isPin && credential.matches(expectedCredential)
+                    credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+                        credential.isPassword && credential.matches(expectedCredential)
+                    credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+                        credential.isPattern && credential.matches(expectedCredential)
+                    else -> error("Unexpected credential type ${credential.type}!")
+                }
 
-        val failedAttempts = _failedAuthenticationAttempts.value
-        return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
-            AuthenticationResultModel(
-                isSuccessful = isSuccessful,
-                lockoutDurationMs = 0,
-            )
-        } else {
-            AuthenticationResultModel(
-                isSuccessful = false,
-                lockoutDurationMs = LOCKOUT_DURATION_MS,
-            )
+            val failedAttempts = _failedAuthenticationAttempts.value
+            if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+                AuthenticationResultModel(
+                    isSuccessful = isSuccessful,
+                    lockoutDurationMs = 0,
+                )
+            } else {
+                AuthenticationResultModel(
+                    isSuccessful = false,
+                    lockoutDurationMs = LOCKOUT_DURATION_MS,
+                )
+            }
         }
     }
 
@@ -155,6 +161,23 @@
         _isPinEnhancedPrivacyEnabled.value = isEnabled
     }
 
+    /**
+     * Pauses any future credential checking. The test must call [unpauseCredentialChecking] to
+     * flush the accumulated credential checks.
+     */
+    suspend fun pauseCredentialChecking() {
+        credentialCheckingMutex.lock()
+    }
+
+    /**
+     * Unpauses future credential checking, if it was paused using [pauseCredentialChecking]. This
+     * doesn't flush any pending coroutine jobs; the test code may still choose to do that using
+     * `runCurrent`.
+     */
+    fun unpauseCredentialChecking() {
+        credentialCheckingMutex.unlock()
+    }
+
     private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
         return when (val credentialType = getCurrentCredentialType(securityMode)) {
             LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index 9ce9ff2..4a02f6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
+import com.android.internal.logging.uiEventLogger
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.bouncer.data.repository.bouncerRepository
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
@@ -23,6 +24,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.sessionTracker
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 
@@ -34,6 +36,8 @@
         deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
         falsingInteractor = falsingInteractor,
         powerInteractor = powerInteractor,
+        uiEventLogger = uiEventLogger,
+        sessionTracker = sessionTracker,
         sceneInteractor = sceneInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
index 5c3e1f4..60d97d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.bouncer.shared.flag
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 
-var Kosmos.fakeComposeBouncerFlags by
-    Kosmos.Fixture { FakeComposeBouncerFlags(fakeSceneContainerFlags) }
+var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
 val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
index c116bbd..7482c0f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
@@ -16,14 +16,11 @@
 
 package com.android.systemui.bouncer.shared.flag
 
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 
-class FakeComposeBouncerFlags(
-    private val sceneContainerFlags: SceneContainerFlags,
-    var composeBouncerEnabled: Boolean = false
-) : ComposeBouncerFlags {
+class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags {
     override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
-        return sceneContainerFlags.isEnabled() || composeBouncerEnabled
+        return SceneContainerFlag.isEnabled || composeBouncerEnabled
     }
 
     @Deprecated(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 4b6ef37..3dd382f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.settings.userTracker
 import com.android.systemui.smartspace.data.repository.smartspaceRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -46,21 +45,20 @@
         broadcastDispatcher = broadcastDispatcher,
         communalRepository = communalRepository,
         widgetRepository = communalWidgetRepository,
-        mediaRepository = communalMediaRepository,
         communalPrefsRepository = communalPrefsRepository,
+        mediaRepository = communalMediaRepository,
         smartspaceRepository = smartspaceRepository,
-        appWidgetHost = mock(),
         keyguardInteractor = keyguardInteractor,
+        communalSettingsInteractor = communalSettingsInteractor,
+        appWidgetHost = mock(),
         editWidgetsActivityStarter = editWidgetsActivityStarter,
         userTracker = userTracker,
         activityStarter = activityStarter,
         userManager = userManager,
         dockManager = fakeDockManager,
+        sceneInteractor = sceneInteractor,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
-        communalSettingsInteractor = communalSettingsInteractor,
-        sceneInteractor = sceneInteractor,
-        sceneContainerFlags = sceneContainerFlags,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index e21c766..2e751cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -24,12 +24,9 @@
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor.ConfigurationBasedDimensions
@@ -49,7 +46,6 @@
     @JvmStatic
     fun create(
         featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
-        sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
         repository: FakeKeyguardRepository = FakeKeyguardRepository(),
         commandQueue: FakeCommandQueue = FakeCommandQueue(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -88,7 +84,6 @@
             repository = repository,
             commandQueue = commandQueue,
             featureFlags = featureFlags,
-            sceneContainerFlags = sceneContainerFlags,
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
             shadeRepository = shadeRepository,
@@ -96,13 +91,12 @@
             KeyguardInteractor(
                 repository = repository,
                 commandQueue = commandQueue,
-                sceneContainerFlags = sceneContainerFlags,
+                powerInteractor = powerInteractor,
                 bouncerRepository = bouncerRepository,
                 configurationInteractor = ConfigurationInteractor(configurationRepository),
                 shadeRepository = shadeRepository,
-                sceneInteractorProvider = { sceneInteractor },
                 keyguardTransitionInteractor = keyguardTransitionInteractor,
-                powerInteractor = powerInteractor,
+                sceneInteractorProvider = { sceneInteractor },
                 fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
                 sharedNotificationContainerInteractor = { sncInteractor },
                 applicationScope = testScope,
@@ -114,7 +108,6 @@
         val repository: FakeKeyguardRepository,
         val commandQueue: FakeCommandQueue,
         val featureFlags: FakeFeatureFlags,
-        val sceneContainerFlags: SceneContainerFlags,
         val bouncerRepository: FakeKeyguardBouncerRepository,
         val configurationRepository: FakeConfigurationRepository,
         val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 2a0c01c..9426718 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -34,7 +33,6 @@
             repository = keyguardRepository,
             commandQueue = commandQueue,
             powerInteractor = powerInteractor,
-            sceneContainerFlags = sceneContainerFlags,
             bouncerRepository = keyguardBouncerRepository,
             configurationInteractor = configurationInteractor,
             shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 709f864..58b0ff8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +46,6 @@
         transitionInteractor = keyguardTransitionInteractor,
         keyguardInteractor = keyguardInteractor,
         viewModel = aodToLockscreenTransitionViewModel,
-        sceneContainerFlags = sceneContainerFlags,
         keyguardViewController = { statusBarKeyguardViewManager },
         deviceEntryInteractor = deviceEntryInteractor,
         deviceEntrySourceInteractor = deviceEntrySourceInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index fdc3e0a..162f278 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -48,10 +48,9 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.sceneContainerConfig
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.scene.shared.model.sceneDataSource
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.shade.shadeController
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.screenOffAnimationController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -71,8 +70,6 @@
     val testDispatcher by lazy { kosmos.testDispatcher }
     val testScope by lazy { kosmos.testScope }
     val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
-    val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
-    val sceneContainerFlags by lazy { kosmos.sceneContainerFlags }
     val fakeExecutor by lazy { kosmos.fakeExecutor }
     val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler }
     val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -112,6 +109,7 @@
     }
     val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
     val qsLongPressEffect by lazy { kosmos.qsLongPressEffect }
+    val shadeController by lazy { kosmos.shadeController }
 
     init {
         kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
similarity index 67%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
index 979d8e7..eac9149 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.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.
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.log
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.sessionTracker: SessionTracker by Kosmos.Fixture { sessionTrackerMock }
+val Kosmos.sessionTrackerMock: SessionTracker by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7ce810e..7dab5c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -17,5 +17,6 @@
 package com.android.systemui.media.controls.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
 
-val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository() }
+val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository(systemClock) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
index da2170c..2f3d3c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
@@ -27,6 +28,7 @@
         MediaControlViewModel(
             applicationContext = applicationContext,
             backgroundDispatcher = testDispatcher,
+            backgroundExecutor = fakeExecutor,
             interactor = mediaControlInteractor,
             logger = mediaUiEventLogger,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
index 6f652f2..e88d22a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
@@ -18,9 +18,5 @@
 
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 
-val Kosmos.mediaFlags by
-    Kosmos.Fixture {
-        MediaFlags(featureFlags = featureFlagsClassic, sceneContainerFlags = sceneContainerFlags)
-    }
+val Kosmos.mediaFlags by Kosmos.Fixture { MediaFlags(featureFlags = featureFlagsClassic) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
index 979d8e7..339f3bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.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.
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.qs.tiles.impl.reducebrightness
 
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
 
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.qsReduceBrightColorsTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideReduceBrightColorsTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
deleted file mode 100644
index ded7256..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.flag
-
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-
-class FakeSceneContainerFlags(
-    var enabled: Boolean = SceneContainerFlag.isEnabled,
-) : SceneContainerFlags {
-
-    override fun isEnabled(): Boolean {
-        return enabled
-    }
-
-    override fun requirementDescription(): String {
-        return ""
-    }
-}
-
-@Module(includes = [FakeSceneContainerFlagsModule.Bindings::class])
-class FakeSceneContainerFlagsModule(
-    @get:Provides val sceneContainerFlags: FakeSceneContainerFlags = FakeSceneContainerFlags(),
-) {
-    @Module
-    interface Bindings {
-        @Binds fun bindFake(fake: FakeSceneContainerFlags): SceneContainerFlags
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 07e2d6b..543d5b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.ShadeModule
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
@@ -36,7 +35,6 @@
 var Kosmos.baseShadeInteractor: BaseShadeInteractor by
     Kosmos.Fixture {
         ShadeModule.provideBaseShadeInteractor(
-            sceneContainerFlags = sceneContainerFlags,
             sceneContainerOn = { shadeInteractorSceneContainerImpl },
             sceneContainerOff = { shadeInteractorLegacyImpl },
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
new file mode 100644
index 0000000..8d653f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
+import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.mobileIconsViewModel
+
+val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
+    Kosmos.Fixture {
+        ShadeHeaderViewModel(
+            applicationScope = applicationCoroutineScope,
+            context = applicationContext,
+            activityStarter = activityStarter,
+            shadeInteractor = shadeInteractor,
+            mobileIconsInteractor = mobileIconsInteractor,
+            mobileIconsViewModel = mobileIconsViewModel,
+            privacyChipInteractor = privacyChipInteractor,
+            clockInteractor = shadeHeaderClockInteractor,
+            broadcastDispatcher = broadcastDispatcher,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index b249211..f0eea38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
@@ -30,7 +29,6 @@
         dumpManager = dumpManager,
         interactor = notificationStackAppearanceInteractor,
         shadeInteractor = shadeInteractor,
-        flags = sceneContainerFlags,
         featureFlags = featureFlagsClassic,
         keyguardInteractor = keyguardInteractor,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index cf800d0..9c3f510 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.policy.headsUpManager
 
@@ -36,7 +35,7 @@
         headsUpManager = headsUpManager,
         powerInteractor = powerInteractor,
         activeNotificationsInteractor = activeNotificationsInteractor,
-        sceneInteractorProvider = { sceneInteractor },
-        sceneContainerFlags = sceneContainerFlags,
-    )
+    ) {
+        sceneInteractor
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
index 638925d..74b2da4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
new file mode 100644
index 0000000..386c7c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+
+val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
+    Kosmos.Fixture {
+        AirplaneModeInteractor(
+            FakeAirplaneModeRepository(),
+            FakeConnectivityRepository(),
+            FakeMobileConnectionsRepository(),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 8109b60..eb2d6c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -64,8 +64,6 @@
 
     override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
 
-    override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
     private var isInEcmMode: Boolean = false
 
     override suspend fun isInEcmMode(): Boolean = isInEcmMode
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt
new file mode 100644
index 0000000..eb6265a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mobileIconsInteractor: MobileIconsInteractor by
+    Kosmos.Fixture { fakeMobileIconsInteractor }
+val Kosmos.fakeMobileIconsInteractor: FakeMobileIconsInteractor by
+    Kosmos.Fixture { FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt
new file mode 100644
index 0000000..c5f6557
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mobileIconsViewModel: MobileIconsViewModel by
+    Kosmos.Fixture {
+        MobileIconsViewModel(
+            logger = mock(),
+            verboseLogger = mock(),
+            interactor = mobileIconsInteractor,
+            airplaneModeInteractor = airplaneModeInteractor,
+            constants = mock(),
+            flags = featureFlagsClassic,
+            scope = applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 28d632d..331e2fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
index 56c6245..94f0c44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 
-class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
+class FakeUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
 
     private val listeners = mutableListOf<TransitionProgressListener>()
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
index 7c54a57..a0f5b58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
 
-var Kosmos.unfoldTransitionProgressProvider by Fixture { mock<UnfoldTransitionProgressProvider>() }
+val Kosmos.fakeUnfoldTransitionProgressProvider by Fixture { FakeUnfoldTransitionProvider() }
+
+val Kosmos.unfoldTransitionProgressProvider by
+    Fixture<UnfoldTransitionProgressProvider> { fakeUnfoldTransitionProgressProvider }
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
new file mode 100644
index 0000000..1ef3816
--- /dev/null
+++ b/packages/SystemUI/utils/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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 {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "SystemUI-shared-utils",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+        "kotlinx_coroutines",
+    ],
+}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt
new file mode 100644
index 0000000..ed97c60
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt
@@ -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.
+ */
+
+@file:OptIn(ExperimentalTypeInference::class)
+
+package com.android.systemui.utils.coroutines.flow
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to
+ * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that
+ * is running in a different context or concurrently.
+ *
+ * The resulting flow is _cold_, which means that [block] is called every time a terminal operator
+ * is applied to the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope]
+ * can be used from any context, e.g. from a callback-based API. The resulting flow completes as
+ * soon as the code in the [block] completes. [awaitClose] should be used to keep the flow running,
+ * otherwise the channel will be closed immediately when block completes. [awaitClose] argument is
+ * called either when a flow consumer cancels the flow collection or when a callback-based API
+ * invokes [SendChannel.close] manually and is typically used to cleanup the resources after the
+ * completion, e.g. unregister a callback. Using [awaitClose] is mandatory in order to prevent
+ * memory leaks when the flow collection is cancelled, otherwise the callback may keep running even
+ * when the flow collector is already completed. To avoid such leaks, this method throws
+ * [IllegalStateException] if block returns, but the channel is not closed yet.
+ *
+ * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to
+ * specify a user-defined value and to control what happens when data is produced faster than
+ * consumed, i.e. to control the back-pressure behavior.
+ *
+ * Adjacent applications of [callbackFlow], [flowOn], [buffer], and [produceIn] are always fused so
+ * that only one properly configured channel is used for execution.
+ *
+ * Example of usage that converts a multi-shot callback API to a flow. For single-shot callbacks use
+ * [suspendCancellableCoroutine].
+ *
+ * ```
+ * fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
+ *     val callback = object : Callback { // Implementation of some callback interface
+ *         override fun onNextValue(value: T) {
+ *             // To avoid blocking you can configure channel capacity using
+ *             // either buffer(Channel.CONFLATED) or buffer(Channel.UNLIMITED) to avoid overfill
+ *             trySendBlocking(value)
+ *                 .onFailure { throwable ->
+ *                     // Downstream has been cancelled or failed, can log here
+ *                 }
+ *         }
+ *         override fun onApiError(cause: Throwable) {
+ *             cancel(CancellationException("API Error", cause))
+ *         }
+ *         override fun onCompleted() = channel.close()
+ *     }
+ *     api.register(callback)
+ *     /*
+ *      * Suspends until either 'onCompleted'/'onApiError' from the callback is invoked
+ *      * or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled).
+ *      * In both cases, callback will be properly unregistered.
+ *      */
+ *     awaitClose { api.unregister(callback) }
+ * }
+ * ```
+ * > The callback `register`/`unregister` methods provided by an external API must be thread-safe,
+ * > because `awaitClose` block can be called at any time due to asynchronous nature of
+ * > cancellation, even concurrently with the call of the callback.
+ *
+ * This builder is to be preferred over [callbackFlow], due to the latter's default configuration of
+ * using an internal buffer, negatively impacting system health.
+ *
+ * @see callbackFlow
+ */
+fun <T> conflatedCallbackFlow(
+    @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+): Flow<T> = callbackFlow(block).conflate()
+
+/**
+ * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to
+ * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that
+ * is running in a different context or concurrently. The resulting flow is _cold_, which means that
+ * [block] is called every time a terminal operator is applied to the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope]
+ * can be used concurrently from different contexts. The resulting flow completes as soon as the
+ * code in the [block] and all its children completes. Use [awaitClose] as the last statement to
+ * keep it running. A more detailed example is provided in the documentation of [callbackFlow].
+ *
+ * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to
+ * specify a user-defined value and to control what happens when data is produced faster than
+ * consumed, i.e. to control the back-pressure behavior.
+ *
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are always fused so
+ * that only one properly configured channel is used for execution.
+ *
+ * Examples of usage:
+ * ```
+ * fun <T> Flow<T>.merge(other: Flow<T>): Flow<T> = channelFlow {
+ *     // collect from one coroutine and send it
+ *     launch {
+ *         collect { send(it) }
+ *     }
+ *     // collect and send from this coroutine, too, concurrently
+ *     other.collect { send(it) }
+ * }
+ *
+ * fun <T> contextualFlow(): Flow<T> = channelFlow {
+ *     // send from one coroutine
+ *     launch(Dispatchers.IO) {
+ *         send(computeIoValue())
+ *     }
+ *     // send from another coroutine, concurrently
+ *     launch(Dispatchers.Default) {
+ *         send(computeCpuValue())
+ *     }
+ * }
+ * ```
+ *
+ * This builder is to be preferred over [channelFlow], due to the latter's default configuration of
+ * using an internal buffer, negatively impacting system health.
+ *
+ * @see channelFlow
+ */
+fun <T> conflatedChannelFlow(
+    @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+): Flow<T> = channelFlow(block).conflate()
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
new file mode 100644
index 0000000..5f8c660
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+
+package com.android.systemui.utils.coroutines.flow
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * Returns a flow that emits elements from the original flow transformed by [transform] function.
+ * When the original flow emits a new value, computation of the [transform] block for previous value
+ * is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ *     emit("a")
+ *     delay(100)
+ *     emit("b")
+ * }.mapLatest { value ->
+ *     println("Started computing $value")
+ *     delay(200)
+ *     "Computed $value"
+ * }
+ * ```
+ *
+ * will print "Started computing a" and "Started computing b", but the resulting flow will contain
+ * only "Computed b" value.
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [mapLatest], due to the latter's default configuration of using an internal buffer, negatively
+ * impacting system health.
+ *
+ * @see mapLatest
+ */
+fun <T, R> Flow<T>.mapLatestConflated(@BuilderInference transform: suspend (T) -> R): Flow<R> =
+    mapLatest(transform).conflate()
+
+/**
+ * Returns a flow that switches to a new flow produced by [transform] function every time the
+ * original flow emits a value. When the original flow emits a new value, the previous flow produced
+ * by `transform` block is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ *     emit("a")
+ *     delay(100)
+ *     emit("b")
+ * }.flatMapLatest { value ->
+ *     flow {
+ *         emit(value)
+ *         delay(200)
+ *         emit(value + "_last")
+ *     }
+ * }
+ * ```
+ *
+ * produces `a b b_last`
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [flatMapLatest], due to the latter's default configuration of using an internal buffer,
+ * negatively impacting system health.
+ *
+ * @see flatMapLatest
+ */
+fun <T, R> Flow<T>.flatMapLatestConflated(
+    @BuilderInference transform: suspend (T) -> Flow<R>,
+): Flow<R> = flatMapLatest(transform).conflate()
+
+/**
+ * Returns a flow that produces element by [transform] function every time the original flow emits a
+ * value. When the original flow emits a new value, the previous `transform` block is cancelled,
+ * thus the name `transformLatest`.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ *     emit("a")
+ *     delay(100)
+ *     emit("b")
+ * }.transformLatest { value ->
+ *     emit(value)
+ *     delay(200)
+ *     emit(value + "_last")
+ * }
+ * ```
+ *
+ * produces `a b b_last`.
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [transformLatest], due to the latter's default configuration of using an internal buffer,
+ * negatively impacting system health.
+ *
+ * @see transformLatest
+ */
+fun <T, R> Flow<T>.transformLatestConflated(
+    @BuilderInference transform: suspend FlowCollector<R>.(T) -> Unit,
+): Flow<R> = transformLatest(transform).conflate()
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 23e269a..cbbce1a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -19,6 +19,7 @@
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
@@ -39,6 +40,7 @@
 import android.content.SharedPreferences;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.graphics.BitmapFactory;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -109,22 +111,16 @@
     static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
     @VisibleForTesting
     static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
-
     @VisibleForTesting
     static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
-
     static final String EMPTY_SENTINEL = "empty";
     static final String QUOTA_SENTINEL = "quota";
-
     // Shared preferences constants.
     static final String PREFS_NAME = "wbprefs.xml";
     static final String SYSTEM_GENERATION = "system_gen";
     static final String LOCK_GENERATION = "lock_gen";
 
-    /**
-     * An approximate area threshold to compare device dimension similarity
-     */
-    static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+    static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f;
 
     // If this file exists, it means we exceeded our quota last time
     private File mQuotaFile;
@@ -336,7 +332,6 @@
         mEventLogger.onSystemImageWallpaperBackupFailed(error);
     }
 
-
     private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
             boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
         final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
@@ -409,6 +404,16 @@
         }
     }
 
+    private static String readText(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String result = "";
+        if (parser.next() == XmlPullParser.TEXT) {
+            result = parser.getText();
+            parser.nextTag();
+        }
+        return result;
+    }
+
     @VisibleForTesting
     // fullBackupFile is final, so we intercept backups here in tests.
     protected void backupFile(File file, FullBackupDataOutput data) {
@@ -438,18 +443,10 @@
         boolean lockImageStageExists = lockImageStage.exists();
 
         try {
-            // Parse the device dimensions of the source device and compare with target to
-            // to identify whether we need to skip the remainder of the restore process
+            // Parse the device dimensions of the source device
             Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
                     deviceDimensionsStage);
 
-            Point targetDeviceDimensions = getScreenDimensions();
-            if (sourceDeviceDimensions != null && targetDeviceDimensions != null
-                    && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
-                    targetDeviceDimensions)) {
-                Slog.d(TAG, "The source device is significantly smaller than target");
-            }
-
             // First parse the live component name so that we know for logging if we care about
             // logging errors with the image restore.
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -466,9 +463,10 @@
             // to back up the original image on the source device, or there was no user-supplied
             // wallpaper image present.
             if (lockImageStageExists) {
-                restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+                restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK,
+                        sourceDeviceDimensions);
             }
-            restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+            restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions);
 
             // And reset to the wallpaper service we should be using
             if (mLockHasLiveComponent) {
@@ -543,16 +541,6 @@
         }
     }
 
-    private static String readText(TypedXmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        String result = "";
-        if (parser.next() == XmlPullParser.TEXT) {
-            result = parser.getText();
-            parser.nextTag();
-        }
-        return result;
-    }
-
     @VisibleForTesting
     void updateWallpaperComponent(ComponentName wpService, int which)
             throws IOException {
@@ -578,10 +566,13 @@
         }
     }
 
-    private void restoreFromStage(File stage, File info, String hintTag, int which)
+    private void restoreFromStage(File stage, File info, String hintTag, int which,
+            Pair<Point, Point> sourceDeviceDimensions)
             throws IOException {
         if (stage.exists()) {
             if (multiCrop()) {
+                // TODO(b/332937943): implement offset adjustment by manually adjusting crop to
+                //  adhere to device aspect ratio
                 SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
                 if (cropHints != null) {
                     Slog.i(TAG, "Got restored wallpaper; applying which=" + which
@@ -601,7 +592,6 @@
                 }
                 return;
             }
-
             // Parse the restored info file to find the crop hint.  Note that this currently
             // relies on a priori knowledge of the wallpaper info file schema.
             Rect cropHint = parseCropHint(info, hintTag);
@@ -609,8 +599,33 @@
                 Slog.i(TAG, "Got restored wallpaper; applying which=" + which
                         + "; cropHint = " + cropHint);
                 try (FileInputStream in = new FileInputStream(stage)) {
-                    mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
-                            which);
+
+                    if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        options.inJustDecodeBounds = true;
+                        ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY);
+                        BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(),
+                                null, options);
+                        Point bitmapSize = new Point(options.outWidth, options.outHeight);
+                        Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x,
+                                sourceDeviceDimensions.first.y);
+                        Point targetDeviceDimensions = getScreenDimensions();
+
+                        // TODO: for now we handle only the case where the target device has smaller
+                        // aspect ratio than the source device i.e. the target device is more narrow
+                        // than the source device
+                        if (isTargetMoreNarrowThanSource(targetDeviceDimensions,
+                                sourceDeviceSize)) {
+                            Rect adjustedCrop = findNewCropfromOldCrop(cropHint,
+                                    sourceDeviceDimensions.first, true, targetDeviceDimensions,
+                                    bitmapSize, true);
+
+                            cropHint.set(adjustedCrop);
+                        }
+                    }
+
+                    mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint,
+                            true, which);
 
                     // And log the success
                     if ((which & FLAG_SYSTEM) > 0) {
@@ -629,6 +644,209 @@
         }
     }
 
+    /**
+     * This method computes the crop of the stored wallpaper to preserve its center point as the
+     * user had set it in the previous device.
+     *
+     * The algorithm involves first computing the original crop of the user (without parallax). Then
+     * manually adjusting the user's original crop to respect the current device's aspect ratio
+     * (thereby preserving the center point). Then finally, adding any leftover image real-estate
+     * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added
+     * if was present in the old device's settings.
+     *
+     */
+    private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl,
+            Point newDisplaySize, Point bitmapSize, boolean newRtl) {
+        Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize);
+        oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop;
+        float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1;
+
+        Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize,
+                cropWithoutParallax);
+
+        Rect newCrop = newCropWithSameCenterWithoutParallax;
+
+        // calculate the amount of left-over space there is in the image after adjusting the crop
+        // from the above operation i.e. in a rtl configuration, this is the remaining space in the
+        // image after subtracting the new crop's right edge coordinate from the image itself, and
+        // for ltr, its just the new crop's left edge coordinate (as it's the distance from the
+        // beginning of the image)
+        int widthAvailableForParallaxOnTheNewDevice =
+                (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right;
+
+        // calculate relatively how much this available space is as a fraction of the total cropped
+        // image
+        float availableParallaxAmount =
+                (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width();
+
+        float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount);
+
+        if (DEBUG) {
+            Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax);
+            Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount);
+            Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: "
+                    + newCropWithSameCenterWithoutParallax);
+            Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: "
+                    + widthAvailableForParallaxOnTheNewDevice);
+            Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount);
+            Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax);
+            Slog.d(TAG, "- oldCrop: " + oldCrop);
+            Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize);
+            Slog.d(TAG, "- oldRtl: " + oldRtl);
+            Slog.d(TAG, "- newDisplaySize: " + newDisplaySize);
+            Slog.d(TAG, "- bitmapSize: " + bitmapSize);
+            Slog.d(TAG, "- newRtl: " + newRtl);
+        }
+        if (availableParallaxAmount >= minAcceptableParallax) {
+            // but in any case, don't put more parallax than the amount of the old device
+            float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount);
+
+            int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd);
+            if (DEBUG) {
+                Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd);
+                Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax);
+            }
+            if (newRtl) {
+                newCrop.left -= widthToAddForParallax;
+            } else {
+                newCrop.right += widthToAddForParallax;
+            }
+        }
+        return newCrop;
+    }
+
+    /**
+     * This method computes the original crop of the user without parallax.
+     *
+     * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added
+     * to the crop to support parallax. In order to determine the user's actual crop the parallax
+     * must be removed if it exists.
+     */
+    Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) {
+        // in the case an image's crop is not set, we assume the image itself is cropped
+        if (crop.isEmpty()) {
+            crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        }
+
+        if (DEBUG) {
+            Slog.w(TAG, "- crop: " + crop);
+        }
+
+        Rect adjustedCrop = new Rect(crop);
+        float suggestedDisplayRatio = (float) displaySize.x / displaySize.y;
+
+        // here we calculate the width of the wallpaper image such that it has the same aspect ratio
+        // as the given display i.e. the width of the image on a single page of the device without
+        // parallax (i.e. displaySize will correspond to the display the crop was originally set on)
+        int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height()
+                / displaySize.y);
+        // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of
+        // parallax added
+        int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax);
+
+        if (DEBUG) {
+            Slog.d(TAG, "- adjustedCrop: " + adjustedCrop);
+            Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio);
+            Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax);
+            Slog.d(TAG, "- widthToRemove: " + widthToRemove);
+        }
+        if (rtl) {
+            adjustedCrop.left += widthToRemove;
+        } else {
+            adjustedCrop.right -= widthToRemove;
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "- adjustedCrop: " + crop);
+        }
+        return adjustedCrop;
+    }
+
+    /**
+     * This method computes a new crop based on the given crop in order to preserve the center point
+     * of the given crop on the provided displaySize. This is only for the case where the device
+     * displaySize has a smaller aspect ratio than the cropped image.
+     *
+     * NOTE: If the width to height ratio is less in the device display than cropped image
+     * this means the aspect ratios are off and there will be distortions in the image
+     * if the image is applied to the current display (i.e. the image will be skewed ->
+     * pixels in the image will not align correctly with the same pixels in the image that are
+     * above them)
+     */
+    Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) {
+
+        // in the case an image's crop is not set, we assume the image itself is cropped
+        if (crop.isEmpty()) {
+            crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        }
+
+        float screenRatio = (float) displaySize.x / displaySize.y;
+        float cropRatio = (float) crop.width() / crop.height();
+
+        Rect adjustedCrop = new Rect(crop);
+
+        if (screenRatio < cropRatio) {
+            // the screen is more narrow than the image, and as such, the image will need to be
+            // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust
+            // the image's crop in order for it to fit into the screen without having the framework
+            // do it (since the framework left aligns the image after zooming)
+
+            // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio
+            // of the device. To calculate the height, we will use the width of the current crop.
+            // This is so we find the largest height possible which also respects the device aspect
+            // ratio.
+            int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height());
+
+            // Calculate how much extra image space available that can be used to adjust
+            // the crop. If this amount is less than heightToAdd, from above, then that means we
+            // can't use heightToAdd. Instead we will need to use the maximum possible height, which
+            // is the height of the original bitmap. NOTE: the bitmap height may be different than
+            // the crop.
+            // since there is no guarantee to have height available on both sides
+            // (e.g. the available height might be fully at the bottom), grab the minimum
+            int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom);
+            int actualHeightToAdd = Math.min(heightToAdd, availableHeight);
+
+            // half of the additional height is added to the top and bottom of the crop
+            adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2;
+            adjustedCrop.bottom += actualHeightToAdd / 2;
+
+            // Calculate the width of the adjusted crop. Initially we used the fixed width of the
+            // crop to calculate the heightToAdd, but since this height may be invalid (based on
+            // the calculation above) we calculate the width again instead of using the fixed width,
+            // using the adjustedCrop's updated height.
+            int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio);
+
+            // half of the additional width is subtracted from the left and right side of the crop
+            int widthToRemoveLeft = widthToRemove / 2;
+            int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2;
+
+            adjustedCrop.left += widthToRemoveLeft;
+            adjustedCrop.right -= widthToRemoveRight;
+
+            if (DEBUG) {
+                Slog.d(TAG, "cropRatio: " + cropRatio);
+                Slog.d(TAG, "screenRatio: " + screenRatio);
+                Slog.d(TAG, "heightToAdd: " + heightToAdd);
+                Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd);
+                Slog.d(TAG, "availableHeight: " + availableHeight);
+                Slog.d(TAG, "widthToRemove: " + widthToRemove);
+                Slog.d(TAG, "adjustedCrop: " + adjustedCrop);
+            }
+
+            return adjustedCrop;
+        }
+
+        return adjustedCrop;
+    }
+
+    private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) {
+        float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y;
+        float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y;
+
+        return (targetScreenRatio < srcScreenRatio);
+    }
+
     private void logRestoreErrorIfNoLiveComponent(int which, String error) {
         if (mSystemHasLiveComponent) {
             return;
@@ -644,6 +862,7 @@
             mEventLogger.onLockImageWallpaperRestoreFailed(error);
         }
     }
+
     private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
         Rect cropHint = new Rect();
         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
@@ -681,7 +900,7 @@
                 if (type != XmlPullParser.START_TAG) continue;
                 String tag = parser.getName();
                 if (!sectionTag.equals(tag)) continue;
-                for (Pair<Integer, String> pair: List.of(
+                for (Pair<Integer, String> pair : List.of(
                         new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
                         new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
                         new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
@@ -907,22 +1126,6 @@
         return internalDisplays;
     }
 
-    /**
-     * This method compares the source and target dimensions, and returns true if there is a
-     * significant difference in area between them and the source dimensions are smaller than the
-     * target dimensions.
-     *
-     * @param sourceDimensions is the dimensions of the source device
-     * @param targetDimensions is the dimensions of the target device
-     */
-    @VisibleForTesting
-    boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
-            Point targetDimensions) {
-        int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
-                - (sourceDimensions.x * sourceDimensions.y);
-        return rawAreaDelta > AREA_THRESHOLD;
-    }
-
     @VisibleForTesting
     boolean isDeviceInRestore() {
         try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index ec9223c..3ecdf3f 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,7 +59,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -841,26 +840,6 @@
         testParseCropHints(testMap);
     }
 
-    @Test
-    public void test_sourceDimensionsAreLargerThanTarget() {
-        // source device is larger than target, expecting to get false
-        Point sourceDimensions = new Point(2208, 1840);
-        Point targetDimensions = new Point(1080, 2092);
-        boolean isSourceSmaller = mWallpaperBackupAgent
-                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
-        assertThat(isSourceSmaller).isEqualTo(false);
-    }
-
-    @Test
-    public void test_sourceDimensionsMuchSmallerThanTarget() {
-        // source device is smaller than target, expecting to get true
-        Point sourceDimensions = new Point(1080, 2092);
-        Point targetDimensions = new Point(2208, 1840);
-        boolean isSourceSmaller = mWallpaperBackupAgent
-                .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
-        assertThat(isSourceSmaller).isEqualTo(true);
-    }
-
     private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
         assumeTrue(multiCrop());
         mockRestoredStaticWallpaperFile(testMap);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index be2ad21..d279bd5 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -852,11 +852,9 @@
         final int pointerIdBits = (1 << pointerId);
         if (mSendHoverEnterAndMoveDelayed.isPending()) {
             // If we have not delivered the enter schedule an exit.
-            if (Flags.resetHoverEventTimerOnActionUp()) {
-                // We cancel first to reset the time window so that the user has the full amount of
-                // time to do a multi tap.
-                mSendHoverEnterAndMoveDelayed.repost();
-            }
+            // We cancel first to reset the time window so that the user has the full amount of
+            // time to do a multi tap.
+            mSendHoverEnterAndMoveDelayed.repost();
             mSendHoverExitDelayed.post(event, rawEvent, pointerIdBits, policyFlags);
         } else {
             // The user is touch exploring so we send events for end.
@@ -1601,7 +1599,7 @@
                                 + " pointers down.");
                 return;
             }
-            if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) {
+            if (mEvents.size() == 0) {
                 return;
             }
             // Send an accessibility event to announce the touch exploration start.
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index ced10fb..70ecc05 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -41,3 +41,10 @@
   description: "Use weak reference to address binder leak problem"
   bug: "307972253"
 }
+
+flag {
+  name: "include_last_focused_id_and_session_id_in_client_state"
+  namespace: "autofill"
+  description: "Include the current view id and session id into the FillEventHistory as part of ClientState"
+  bug: "334141398"
+}
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 4506779..ce9d180 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,21 +16,16 @@
 
 package com.android.server.autofill;
 
-import static com.android.server.autofill.Session.REQUEST_ID_KEY;
-import static com.android.server.autofill.Session.SESSION_ID_KEY;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentSender;
-import android.os.Bundle;
 import android.service.autofill.ConvertCredentialResponse;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.util.Slog;
 import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
 
 /**
  * Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -55,6 +50,7 @@
 
     private final RemoteFillService mRemoteFillService;
     private final SecondaryProviderCallback mCallback;
+
     private int mLastFlag;
 
     SecondaryProviderHandler(
@@ -109,37 +105,18 @@
     /**
      * Requests a new fill response.
      */
-    public void onFillRequest(FillRequest pendingFillRequest,
-            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int flag, int id,
+    public void onFillRequest(FillRequest pendingFillRequest, int flag,
             IAutoFillManagerClient client) {
         Slog.v(TAG, "Requesting fill response to secondary provider.");
         mLastFlag = flag;
         if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
             Slog.v(TAG, "About to call CredAutofill service as secondary provider");
-            FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
-                    pendingInlineSuggestionsRequest, id);
-            mRemoteFillService.onFillCredentialRequest(request, client);
+            mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
         } else {
             mRemoteFillService.onFillRequest(pendingFillRequest);
         }
     }
 
-    private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
-            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
-        if (pendingFillRequest.getClientState() == null) {
-            pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
-                    pendingFillRequest.getFillContexts(),
-                    pendingFillRequest.getHints(),
-                    new Bundle(),
-                    pendingFillRequest.getFlags(),
-                    pendingInlineSuggestionsRequest,
-                    pendingFillRequest.getDelayedFillIntentSender());
-        }
-        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
-        pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
-        return pendingFillRequest;
-    }
-
     public void destroy() {
         mRemoteFillService.destroy();
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0b68f5f..3a38406 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -117,6 +117,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ServiceInfo;
+import android.credentials.CredentialManager;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
 import android.graphics.Bitmap;
@@ -252,7 +253,6 @@
     private final AutofillManagerServiceImpl mService;
     private final Handler mHandler;
     private final AutoFillUI mUi;
-
     /**
      * Context associated with the session, it has the same {@link Context#getDisplayId() displayId}
      * of the activity being autofilled.
@@ -751,12 +751,17 @@
             if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
                     && mSecondaryProviderHandler != null) {
                 Slog.v(TAG, "Requesting fill response to secondary provider.");
+                if (!mIsPrimaryCredential) {
+                    mPendingFillRequest = addCredentialManagerDataToClientState(
+                            mPendingFillRequest,
+                            mPendingInlineSuggestionsRequest, id);
+                }
                 mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
-                        mPendingInlineSuggestionsRequest,
-                        mPendingFillRequest.getFlags(), id, mClient);
+                        mPendingFillRequest.getFlags(), mClient);
             } else if (mRemoteFillService != null) {
                 if (mIsPrimaryCredential) {
-                    mPendingFillRequest = addSessionIdAndRequestIdToClientState(mPendingFillRequest,
+                    mPendingFillRequest = addCredentialManagerDataToClientState(
+                            mPendingFillRequest,
                             mPendingInlineSuggestionsRequest, id);
                     mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
                 } else {
@@ -904,8 +909,9 @@
         }
     }
 
-    private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+    private FillRequest addCredentialManagerDataToClientState(FillRequest pendingFillRequest,
             InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
+
         if (pendingFillRequest.getClientState() == null) {
             pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
                     pendingFillRequest.getFillContexts(),
@@ -917,6 +923,10 @@
         }
         pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
         pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
+        ResultReceiver resultReceiver = constructCredentialManagerCallback(
+                pendingFillRequest.getId());
+        pendingFillRequest.getClientState().putParcelable(
+                CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver);
         return pendingFillRequest;
     }
 
@@ -4870,10 +4880,6 @@
 
         }
 
-        if (isCredmanIntegrationActive(response)) {
-            addCredentialManagerCallback(response);
-        }
-
         if (response.supportsInlineSuggestions()) {
             synchronized (mLock) {
                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -5153,30 +5159,14 @@
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
 
-    private void addCredentialManagerCallback(FillResponse response) {
-        if (response.getDatasets() == null) {
-            return;
-        }
-        for (Dataset dataset: response.getDatasets()) {
-            if (dataset.getId() != null
-                    && dataset.getId().equals(AutofillManager.PINNED_DATASET_ID)) {
-                Slog.d(TAG, "Adding Credential Manager callback to a pinned entry");
-                addCredentialManagerCallbackForDataset(dataset, response.getRequestId());
-            }
-        }
-    }
-
-    private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
-        AutofillId autofillId = null;
-        if (dataset != null && dataset.getFieldIds().size() == 1) {
-            autofillId = dataset.getFieldIds().get(0);
-        }
-        final AutofillId finalAutofillId = autofillId;
+    private ResultReceiver constructCredentialManagerCallback(int requestId) {
         final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+            final AutofillId mAutofillId = mCurrentViewId;
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
-                    Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
+                    Slog.d(TAG, "onReceiveResult from Credential Manager "
+                            + "bottom sheet with mCurrentViewId: " + mAutofillId);
                     GetCredentialResponse getCredentialResponse =
                             resultData.getParcelable(
                                     CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
@@ -5184,7 +5174,7 @@
 
                     if (Flags.autofillCredmanDevIntegration()) {
                         sendCredentialManagerResponseToApp(getCredentialResponse,
-                                /*exception=*/ null, finalAutofillId);
+                                /*exception=*/ null, mAutofillId);
                     } else {
                         Dataset datasetFromCredential = getDatasetFromCredentialResponse(
                                 getCredentialResponse);
@@ -5198,12 +5188,14 @@
                     String[] exception =  resultData.getStringArray(
                             CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
                     if (exception != null && exception.length >= 2) {
+                        String errType = exception[0];
+                        String errMsg = exception[1];
                         Slog.w(TAG, "Credman bottom sheet from pinned "
-                                + "entry failed with: + " + exception[0] + " , "
-                                + exception[1]);
+                                + "entry failed with: + " + errType + " , "
+                                + errMsg);
                         sendCredentialManagerResponseToApp(/*response=*/ null,
-                                new GetCredentialException(exception[0], exception[1]),
-                                finalAutofillId);
+                                new GetCredentialException(errType, errMsg),
+                                mAutofillId);
                     }
                 } else {
                     Slog.d(TAG, "Unknown resultCode from credential "
@@ -5214,15 +5206,7 @@
         ResultReceiver ipcFriendlyResultReceiver =
                 toIpcFriendlyResultReceiver(resultReceiver);
 
-        Intent metadataIntent = dataset.getCredentialFillInIntent();
-        if (metadataIntent == null) {
-            metadataIntent = new Intent();
-        }
-
-        metadataIntent.putExtra(
-                android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
-                ipcFriendlyResultReceiver);
-        dataset.setCredentialFillInIntent(metadataIntent);
+        return ipcFriendlyResultReceiver;
     }
 
     private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index cfb7f337..af49df6 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -56,6 +56,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
 import com.android.server.companion.association.AssociationStore;
 
 import java.io.PrintWriter;
@@ -1031,6 +1032,9 @@
     public void sendDevicePresenceEventOnUnlocked(int userId) {
         final List<DevicePresenceEvent> deviceEvents = getPendingDevicePresenceEventsByUserId(
                 userId);
+        if (CollectionUtils.isEmpty(deviceEvents)) {
+            return;
+        }
         final List<ObservableUuid> observableUuids =
                 mObservableUuidStore.getObservableUuidsForUser(userId);
         // Notify and bind the app after the phone is unlocked.
@@ -1068,7 +1072,7 @@
             }
         }
 
-        clearPendingDevicePresenceEventsByUserId(userId);
+        removePendingDevicePresenceEventsByUserId(userId);
     }
 
     private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) {
@@ -1077,9 +1081,11 @@
         }
     }
 
-    private void clearPendingDevicePresenceEventsByUserId(int userId) {
+    private void removePendingDevicePresenceEventsByUserId(int userId) {
         synchronized (mPendingDevicePresenceEvents) {
-            mPendingDevicePresenceEvents.get(userId).clear();
+            if (mPendingDevicePresenceEvents.contains(userId)) {
+                mPendingDevicePresenceEvents.remove(userId);
+            }
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 23373f1..afeafa4 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -302,7 +302,7 @@
         if (Flags.interceptIntentsBeforeApplyingPolicy()) {
             if (mIntentListenerCallback != null && intent != null
                     && mIntentListenerCallback.shouldInterceptIntent(intent)) {
-                Slog.d(TAG, "Virtual device intercepting intent");
+                logActivityLaunchBlocked("Virtual device intercepting intent");
                 return false;
             }
             if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
@@ -318,7 +318,7 @@
             }
             if (mIntentListenerCallback != null && intent != null
                     && mIntentListenerCallback.shouldInterceptIntent(intent)) {
-                Slog.d(TAG, "Virtual device intercepting intent");
+                logActivityLaunchBlocked("Virtual device intercepting intent");
                 return false;
             }
         }
@@ -331,15 +331,17 @@
             boolean isNewTask) {
         // Mirror displays cannot contain activities.
         if (waitAndGetIsMirrorDisplay()) {
-            Slog.d(TAG, "Mirror virtual displays cannot contain activities.");
+            logActivityLaunchBlocked("Mirror virtual displays cannot contain activities.");
             return false;
         }
         if (!isWindowingModeSupported(windowingMode)) {
-            Slog.d(TAG, "Virtual device doesn't support windowing mode " + windowingMode);
+            logActivityLaunchBlocked(
+                    "Virtual device doesn't support windowing mode " + windowingMode);
             return false;
         }
         if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
-            Slog.d(TAG, "Virtual device requires android:canDisplayOnRemoteDevices=true");
+            logActivityLaunchBlocked(
+                    "Activity requires android:canDisplayOnRemoteDevices=true");
             return false;
         }
         final UserHandle activityUser =
@@ -350,11 +352,11 @@
             return true;
         }
         if (!activityUser.isSystem() && !mAllowedUsers.contains(activityUser)) {
-            Slog.d(TAG, "Virtual device launch disallowed from user " + activityUser);
+            logActivityLaunchBlocked("Activity launch disallowed from user " + activityUser);
             return false;
         }
         if (!activityMatchesDisplayCategory(activityInfo)) {
-            Slog.d(TAG, "The activity's required display category '"
+            logActivityLaunchBlocked("The activity's required display category '"
                     + activityInfo.requiredDisplayCategory
                     + "' not found on virtual display with the following categories: "
                     + mDisplayCategories);
@@ -363,7 +365,7 @@
         synchronized (mGenericWindowPolicyControllerLock) {
             if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
                     activityComponent)) {
-                Slog.d(TAG, "Virtual device launch disallowed by policy: "
+                logActivityLaunchBlocked("Activity launch disallowed by policy: "
                         + activityComponent);
                 return false;
             }
@@ -371,7 +373,7 @@
         if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
                 && !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
                         mCrossTaskNavigationExemptions, activityComponent)) {
-            Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+            logActivityLaunchBlocked("Cross task navigation disallowed by policy: "
                     + activityComponent);
             return false;
         }
@@ -380,12 +382,18 @@
         // based on FLAG_STREAM_PERMISSIONS
         if (mPermissionDialogComponent != null
                 && mPermissionDialogComponent.equals(activityComponent)) {
+            logActivityLaunchBlocked("Permission dialog not allowed on virtual device");
             return false;
         }
 
         return true;
     }
 
+    private void logActivityLaunchBlocked(String reason) {
+        Slog.d(TAG, "Virtual device activity launch disallowed on display "
+                + waitAndGetDisplayId() + ", reason: " + reason);
+    }
+
     @Override
     @SuppressWarnings("AndroidFrameworkRequiresPermission")
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
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 9b72288..3c25835 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -226,7 +226,7 @@
         token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
         mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
         String phys = inputDeviceDescriptor.getPhys();
-        InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+        InputManagerGlobal.getInstance().removeUniqueIdAssociationByDescriptor(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) {
@@ -319,9 +319,9 @@
         return formatSimple("virtual%s:%d", type, sNextPhysId.getAndIncrement());
     }
 
-    private void setUniqueIdAssociation(int displayId, String phys) {
+    private void setUniqueIdAssociationByPort(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) {
@@ -809,7 +809,7 @@
 
         final int inputDeviceId;
 
-        setUniqueIdAssociation(displayId, phys);
+        setUniqueIdAssociationByPort(displayId, phys);
         try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) {
             ptr = deviceOpener.get();
             // See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
@@ -835,7 +835,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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8022eb3..ee5d49b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4437,8 +4437,16 @@
         final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
                 && packageStateStopped);
         if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
+            final int cancelReason;
+            if (packageName == null) {
+                cancelReason = PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+            } else if (uninstalling) {
+                cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_UNINSTALLED;
+            } else {
+                cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+            }
             didSomething |= mPendingIntentController.removePendingIntentsForPackage(
-                    packageName, userId, appId, doit);
+                    packageName, userId, appId, doit, cancelReason);
         }
 
         if (doit) {
@@ -9181,6 +9189,11 @@
     private class MyBinderProxyCountEventListener implements BinderProxyCountEventListener {
         @Override
         public void onLimitReached(int uid) {
+            // Spawn a new thread for the dump as it'll take long time.
+            new Thread(() -> handleLimitReached(uid), "BinderProxy Dump: " + uid).start();
+        }
+
+        private void handleLimitReached(int uid) {
             Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
                     + Process.myUid());
             BinderProxy.dumpProxyDebugInfo();
@@ -10167,7 +10180,11 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
+        mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+                ALLOW_NON_FULL, "addApplicationStartInfoCompleteListener", null);
+
+        mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener,
+                UserHandle.getUid(userId, UserHandle.getAppId(callingUid)));
     }
 
 
@@ -10182,13 +10199,30 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid,
-                true);
+        mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+                ALLOW_NON_FULL, "removeApplicationStartInfoCompleteListener", null);
+
+        mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener,
+                UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), true);
     }
 
     @Override
     public void addStartInfoTimestamp(int key, long timestampNs, int userId) {
         enforceNotIsolatedCaller("addStartInfoTimestamp");
+
+        // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+            throw new IllegalArgumentException("Unsupported userId");
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+                ALLOW_NON_FULL, "addStartInfoTimestamp", null);
+
+        final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+
+        mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
+                UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index ddf1d5f..0728ea8 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -464,7 +464,7 @@
         addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
     }
 
-    private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
+    void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
         synchronized (mLock) {
             AppStartInfoContainer container = mData.get(packageName, uid);
             if (container == null) {
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index fb0d695..f336120 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -22,6 +22,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -54,6 +56,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
+import com.android.server.am.PendingIntentRecord.CancellationReason;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.SafeActivityOptions;
 
@@ -191,7 +194,7 @@
                     }
                     return rec;
                 }
-                makeIntentSenderCanceled(rec);
+                makeIntentSenderCanceled(rec, CANCEL_REASON_SUPERSEDED);
                 mIntentSenderRecords.remove(key);
                 decrementUidStatLocked(rec);
             }
@@ -206,7 +209,7 @@
     }
 
     boolean removePendingIntentsForPackage(String packageName, int userId, int appId,
-            boolean doIt) {
+            boolean doIt, @CancellationReason int cancelReason) {
 
         boolean didSomething = false;
         synchronized (mLock) {
@@ -256,7 +259,7 @@
                 }
                 didSomething = true;
                 it.remove();
-                makeIntentSenderCanceled(pir);
+                makeIntentSenderCanceled(pir, cancelReason);
                 decrementUidStatLocked(pir);
                 if (pir.key.activity != null) {
                     final Message m = PooledLambda.obtainMessage(
@@ -289,13 +292,14 @@
             } catch (RemoteException e) {
                 throw new SecurityException(e);
             }
-            cancelIntentSender(rec, true);
+            cancelIntentSender(rec, true, CANCEL_REASON_OWNER_CANCELED);
         }
     }
 
-    public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) {
+    public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity,
+            @CancellationReason int cancelReason) {
         synchronized (mLock) {
-            makeIntentSenderCanceled(rec);
+            makeIntentSenderCanceled(rec, cancelReason);
             mIntentSenderRecords.remove(rec.key);
             decrementUidStatLocked(rec);
             if (cleanActivity && rec.key.activity != null) {
@@ -359,8 +363,10 @@
         }
     }
 
-    private void makeIntentSenderCanceled(PendingIntentRecord rec) {
+    private void makeIntentSenderCanceled(PendingIntentRecord rec,
+            @CancellationReason int cancelReason) {
         rec.canceled = true;
+        rec.cancelReason = cancelReason;
         final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();
         if (callbacks != null) {
             final Message m = PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 95e130e..da45a77 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -16,11 +16,13 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.START_SUCCESS;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
@@ -51,11 +53,15 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.expresslog.Counter;
 import com.android.server.wm.SafeActivityOptions;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.Objects;
 
@@ -71,12 +77,35 @@
     public static final int FLAG_BROADCAST_SENDER = 1 << 1;
     public static final int FLAG_SERVICE_SENDER = 1 << 2;
 
+    public static final int CANCEL_REASON_NULL = 0;
+    public static final int CANCEL_REASON_USER_STOPPED = 1 << 0;
+    public static final int CANCEL_REASON_OWNER_UNINSTALLED = 1 << 1;
+    public static final int CANCEL_REASON_OWNER_FORCE_STOPPED = 1 << 2;
+    public static final int CANCEL_REASON_OWNER_CANCELED = 1 << 3;
+    public static final int CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED = 1 << 4;
+    public static final int CANCEL_REASON_SUPERSEDED = 1 << 5;
+    public static final int CANCEL_REASON_ONE_SHOT_SENT = 1 << 6;
+
+    @IntDef({
+            CANCEL_REASON_NULL,
+            CANCEL_REASON_USER_STOPPED,
+            CANCEL_REASON_OWNER_UNINSTALLED,
+            CANCEL_REASON_OWNER_FORCE_STOPPED,
+            CANCEL_REASON_OWNER_CANCELED,
+            CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED,
+            CANCEL_REASON_SUPERSEDED,
+            CANCEL_REASON_ONE_SHOT_SENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CancellationReason {}
+
     final PendingIntentController controller;
     final Key key;
     final int uid;
     public final WeakReference<PendingIntentRecord> ref;
     boolean sent = false;
     boolean canceled = false;
+    @CancellationReason int cancelReason = CANCEL_REASON_NULL;
     /**
      * Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
      * milliseconds, Integer is allowlist type defined at
@@ -419,12 +448,22 @@
         SafeActivityOptions mergedOptions = null;
         synchronized (controller.mLock) {
             if (canceled) {
+                if (cancelReason == CANCEL_REASON_OWNER_FORCE_STOPPED
+                        && controller.mAmInternal.getUidProcessState(callingUid)
+                                == PROCESS_STATE_TOP) {
+                    Counter.logIncrementWithUid(
+                            "app.value_force_stop_cancelled_pi_sent_from_top_per_caller",
+                            callingUid);
+                    Counter.logIncrementWithUid(
+                            "app.value_force_stop_cancelled_pi_sent_from_top_per_owner",
+                            uid);
+                }
                 return ActivityManager.START_CANCELED;
             }
 
             sent = true;
             if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
-                controller.cancelIntentSender(this, true);
+                controller.cancelIntentSender(this, true, CANCEL_REASON_ONE_SHOT_SENT);
             }
 
             finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent();
@@ -687,6 +726,21 @@
         }
     }
 
+    @VisibleForTesting
+    static String cancelReasonToString(@CancellationReason int cancelReason) {
+        return switch (cancelReason) {
+            case CANCEL_REASON_NULL -> "NULL";
+            case CANCEL_REASON_USER_STOPPED -> "USER_STOPPED";
+            case CANCEL_REASON_OWNER_UNINSTALLED -> "OWNER_UNINSTALLED";
+            case CANCEL_REASON_OWNER_FORCE_STOPPED -> "OWNER_FORCE_STOPPED";
+            case CANCEL_REASON_OWNER_CANCELED -> "OWNER_CANCELED";
+            case CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED -> "HOSTING_ACTIVITY_DESTROYED";
+            case CANCEL_REASON_SUPERSEDED -> "SUPERSEDED";
+            case CANCEL_REASON_ONE_SHOT_SENT -> "ONE_SHOT_SENT";
+            default -> "UNKNOWN";
+        };
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("uid="); pw.print(uid);
                 pw.print(" packageName="); pw.print(key.packageName);
@@ -707,7 +761,8 @@
         }
         if (sent || canceled) {
             pw.print(prefix); pw.print("sent="); pw.print(sent);
-                    pw.print(" canceled="); pw.println(canceled);
+                    pw.print(" canceled="); pw.print(canceled);
+                    pw.print(" cancelReason="); pw.println(cancelReasonToString(cancelReason));
         }
         if (mAllowlistDuration != null) {
             pw.print(prefix);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index be39778..a1f80d0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -70,6 +70,7 @@
 import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled;
 
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
@@ -248,6 +249,7 @@
             Process.ROOT_UID,
             Process.PHONE_UID,
             Process.BLUETOOTH_UID,
+            Process.AUDIOSERVER_UID,
             Process.NFC_UID,
             Process.NETWORK_STACK_UID,
             Process.SHELL_UID};
@@ -2682,6 +2684,15 @@
         }
     }
 
+    /**
+     * When querying the mode these should always be allowed and the checking service might not
+     * have information on them.
+     */
+    private static boolean isOpAllowedForUid(int uid) {
+        return runtimePermissionAppopsMappingEnabled()
+                && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
+    }
+
     @Override
     public int checkOperationRaw(int code, int uid, String packageName,
             @Nullable String attributionTag) {
@@ -2757,6 +2768,9 @@
                     pvr.bypass, true)) {
                 return AppOpsManager.MODE_IGNORED;
             }
+            if (isOpAllowedForUid(uid)) {
+                return MODE_ALLOWED;
+            }
             code = AppOpsManager.opToSwitch(code);
             UidState uidState = getUidStateLocked(uid, false);
             if (uidState != null
@@ -3071,9 +3085,12 @@
                 return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
                         packageName);
             }
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (mAppOpsCheckingService.getUidMode(
+            if (isOpAllowedForUid(uid)) {
+                // Op is always allowed for the UID, do nothing.
+
+                // If there is a non-default per UID policy (we set UID op mode only if
+                // non-default) it takes over, otherwise use the per package policy.
+            } else if (mAppOpsCheckingService.getUidMode(
                             uidState.uid, getPersistentId(virtualDeviceId), switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
@@ -3665,10 +3682,13 @@
             isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
                     virtualDeviceId, pvr.bypass, false);
             final int switchCode = AppOpsManager.opToSwitch(code);
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (mAppOpsCheckingService.getUidMode(
-                            uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+            if (isOpAllowedForUid(uid)) {
+                // Op is always allowed for the UID, do nothing.
+
+                // If there is a non-default per UID policy (we set UID op mode only if
+                // non-default) it takes over, otherwise use the per package policy.
+            } else if (mAppOpsCheckingService.getUidMode(
+                    uidState.uid, getPersistentId(virtualDeviceId), switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
                         uidState.evalMode(
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 570d4e9..e330ed5 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -16,6 +16,11 @@
 
 package com.android.server.audio;
 
+import static android.media.AudioManager.ADJUST_LOWER;
+import static android.media.AudioManager.ADJUST_MUTE;
+import static android.media.AudioManager.ADJUST_RAISE;
+import static android.media.AudioManager.ADJUST_UNMUTE;
+
 import android.content.Context;
 import android.media.AudioManager;
 import android.os.ShellCommand;
@@ -59,6 +64,12 @@
                 return adjMute();
             case "adj-unmute":
                 return adjUnmute();
+            case "adj-volume":
+                return adjVolume();
+            case "set-group-volume":
+                return setGroupVolume();
+            case "adj-group-volume":
+                return adjGroupVolume();
         }
         return 0;
     }
@@ -90,6 +101,12 @@
         pw.println("    mutes the STREAM_TYPE");
         pw.println("  adj-unmute STREAM_TYPE");
         pw.println("    unmutes the STREAM_TYPE");
+        pw.println("  adj-volume STREAM_TYPE <RAISE|LOWER|MUTE|UNMUTE>");
+        pw.println("    Adjusts the STREAM_TYPE volume given the specified direction");
+        pw.println("  set-group-volume GROUP_ID VOLUME_INDEX");
+        pw.println("    Sets the volume for GROUP_ID to VOLUME_INDEX");
+        pw.println("  adj-group-volume GROUP_ID <RAISE|LOWER|MUTE|UNMUTE>");
+        pw.println("    Adjusts the group volume for GROUP_ID given the specified direction");
     }
 
     private int setSurroundFormatEnabled() {
@@ -260,15 +277,48 @@
         return 0;
     }
 
+    private int adjVolume() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        final int stream = readIntArg();
+        final int direction = readDirectionArg();
+        getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+                + stream + ", " + direction + ", 0)");
+        am.adjustStreamVolume(stream, direction, 0);
+        return 0;
+    }
+
+    private int setGroupVolume() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        final int groupId = readIntArg();
+        final int index = readIntArg();
+        getOutPrintWriter().println("calling AudioManager.setVolumeGroupVolumeIndex("
+                + groupId + ", " + index + ", 0)");
+        am.setVolumeGroupVolumeIndex(groupId, index, 0);
+        return 0;
+    }
+
+    private int adjGroupVolume() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        final int groupId = readIntArg();
+        final int direction = readDirectionArg();
+        getOutPrintWriter().println("calling AudioManager.adjustVolumeGroupVolume("
+                + groupId + ", " + direction + ", 0)");
+        am.adjustVolumeGroupVolume(groupId, direction, 0);
+        return 0;
+    }
+
     private int readIntArg() throws IllegalArgumentException {
-        String argText = getNextArg();
+        final String argText = getNextArg();
 
         if (argText == null) {
             getErrPrintWriter().println("Error: no argument provided");
             throw new IllegalArgumentException("No argument provided");
         }
 
-        int argIntVal = Integer.MIN_VALUE;
+        int argIntVal;
         try {
             argIntVal = Integer.parseInt(argText);
         } catch (NumberFormatException e) {
@@ -278,4 +328,21 @@
 
         return argIntVal;
     }
+
+    private int readDirectionArg() throws IllegalArgumentException {
+        final String argText = getNextArg();
+
+        if (argText == null) {
+            getErrPrintWriter().println("Error: no argument provided");
+            throw new IllegalArgumentException("No argument provided");
+        }
+
+        return switch (argText) {
+            case "RAISE" -> ADJUST_RAISE;
+            case "LOWER" -> ADJUST_LOWER;
+            case "MUTE" -> ADJUST_MUTE;
+            case "UNMUTE" -> ADJUST_UNMUTE;
+            default -> throw new IllegalArgumentException("Wrong direction argument: " + argText);
+        };
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 7df63b1..11cca66 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -25,15 +25,12 @@
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -778,13 +775,7 @@
             hidlConfigs = null;
         }
 
-        if (com.android.server.biometrics.Flags.deHidl()) {
-            registerAuthenticators();
-        } else {
-            // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
-            registerAuthenticators(hidlConfigs);
-        }
-
+        registerAuthenticators();
         mInjector.publishBinderService(this, mImpl);
     }
 
@@ -874,7 +865,7 @@
 
             if (faceService != null) {
                 try {
-                    faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+                    faceService.registerAuthenticators(mFaceSensorConfigurations);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "RemoteException when registering face authenticators", e);
                 }
@@ -912,8 +903,7 @@
 
             if (fingerprintService != null) {
                 try {
-                    fingerprintService.registerAuthenticatorsLegacy(
-                            mFingerprintSensorConfigurations);
+                    fingerprintService.registerAuthenticators(mFingerprintSensorConfigurations);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
                 }
@@ -948,78 +938,6 @@
         return configStrings;
     }
 
-    /**
-     * Registers HIDL and AIDL authenticators for all of the available modalities.
-     *
-     * @param hidlSensors Array of {@link SensorConfig} configuration for all of the HIDL sensors
-     *                    available on the device. This array may contain configuration for
-     *                    different modalities and different sensors of the same modality in
-     *                    arbitrary order. Can be null if no HIDL sensors exist on the device.
-     */
-    private void registerAuthenticators(@Nullable SensorConfig[] hidlSensors) {
-        List<FingerprintSensorPropertiesInternal> hidlFingerprintSensors = new ArrayList<>();
-        List<FaceSensorPropertiesInternal> hidlFaceSensors = new ArrayList<>();
-        // Iris doesn't have IrisSensorPropertiesInternal, using SensorPropertiesInternal instead.
-        List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
-
-        if (hidlSensors != null) {
-            for (SensorConfig sensor : hidlSensors) {
-                Slog.d(TAG, "Registering HIDL ID: " + sensor.id + " Modality: " + sensor.modality
-                        + " Strength: " + sensor.strength);
-                switch (sensor.modality) {
-                    case TYPE_FINGERPRINT:
-                        hidlFingerprintSensors.add(
-                                getHidlFingerprintSensorProps(sensor.id, sensor.strength));
-                        break;
-
-                    case TYPE_FACE:
-                        hidlFaceSensors.add(getHidlFaceSensorProps(sensor.id, sensor.strength));
-                        break;
-
-                    case TYPE_IRIS:
-                        hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
-                        break;
-
-                    default:
-                        Slog.e(TAG, "Unknown modality: " + sensor.modality);
-                }
-            }
-        }
-
-        final IFingerprintService fingerprintService = mInjector.getFingerprintService();
-        if (fingerprintService != null) {
-            try {
-                fingerprintService.registerAuthenticators(hidlFingerprintSensors);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
-            }
-        } else if (hidlFingerprintSensors.size() > 0) {
-            Slog.e(TAG, "HIDL fingerprint configuration exists, but FingerprintService is null.");
-        }
-
-        final IFaceService faceService = mInjector.getFaceService();
-        if (faceService != null) {
-            try {
-                faceService.registerAuthenticators(hidlFaceSensors);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException when registering face authenticators", e);
-            }
-        } else if (hidlFaceSensors.size() > 0) {
-            Slog.e(TAG, "HIDL face configuration exists, but FaceService is null.");
-        }
-
-        final IIrisService irisService = mInjector.getIrisService();
-        if (irisService != null) {
-            try {
-                irisService.registerAuthenticators(hidlIrisSensors);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException when registering iris authenticators", e);
-            }
-        } else if (hidlIrisSensors.size() > 0) {
-            Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
-        }
-    }
-
     private void checkInternalPermission() {
         getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
                 "Must have USE_BIOMETRIC_INTERNAL permission");
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index e578861..91cabb5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -21,7 +21,6 @@
 
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 
 /**
  * This class provides the handler to process biometric operations.
@@ -76,11 +75,8 @@
     }
 
     private Handler getNewHandler(String tag, int priority) {
-        if (Flags.deHidl()) {
-            HandlerThread handlerThread = new HandlerThread(tag, priority);
-            handlerThread.start();
-            return new Handler(handlerThread.getLooper());
-        }
-        return new Handler(Looper.getMainLooper());
+        HandlerThread handlerThread = new HandlerThread(tag, priority);
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 7f04628..7a8e25b 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -81,22 +81,6 @@
     boolean isHardwareIgnoringTouches();
 
     /**
-     * Subscribe to context changes.
-     *
-     * Note that this method only notifies for properties that are visible to the HAL.
-     *
-     * @param context context that will be modified when changed
-     * @param consumer callback when the context is modified
-     *
-     * @deprecated instead use {@link BiometricContext#subscribe(OperationContextExt, Consumer,
-     *                                                           Consumer, AuthenticateOptions)}
-     * TODO (b/294161627): Delete this API once Flags.DE_HIDL is removed.
-     */
-    @Deprecated
-    void subscribe(@NonNull OperationContextExt context,
-            @NonNull Consumer<OperationContext> consumer);
-
-    /**
      * Subscribe to context changes and start the HAL operation.
      *
      * Note that this method only notifies for properties that are visible to the HAL.
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index d8dfa60..a17de3d 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -229,16 +229,6 @@
 
     @Override
     public void subscribe(@NonNull OperationContextExt context,
-            @NonNull Consumer<OperationContext> consumer) {
-        mSubscribers.put(context, consumer);
-        // TODO(b/294161627) Combine the getContext/subscribe APIs to avoid race
-        if (context.getDisplayState() != getDisplayState()) {
-            consumer.accept(context.update(this, context.isCrypto()).toAidlContext());
-        }
-    }
-
-    @Override
-    public void subscribe(@NonNull OperationContextExt context,
             @NonNull Consumer<OperationContext> startHalConsumer,
             @NonNull Consumer<OperationContext> updateContextConsumer,
             @Nullable AuthenticateOptions options) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 4fa8741..1e2451c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,7 +35,6 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -117,24 +116,20 @@
 
     @LockoutTracker.LockoutMode
     public int handleFailedAttempt(int userId) {
-        if (Flags.deHidl()) {
-            if (mLockoutTracker != null) {
-                mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
-            }
-            @LockoutTracker.LockoutMode final int lockoutMode =
-                    getLockoutTracker().getLockoutModeForUser(userId);
-            final PerformanceTracker performanceTracker =
-                    PerformanceTracker.getInstanceForSensorId(getSensorId());
-            if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
-                performanceTracker.incrementPermanentLockoutForUser(userId);
-            } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
-                performanceTracker.incrementTimedLockoutForUser(userId);
-            }
-
-            return lockoutMode;
-        } else {
-            return LockoutTracker.LOCKOUT_NONE;
+        if (mLockoutTracker != null) {
+            mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
         }
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(userId);
+        final PerformanceTracker performanceTracker =
+                PerformanceTracker.getInstanceForSensorId(getSensorId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+            performanceTracker.incrementPermanentLockoutForUser(userId);
+        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+            performanceTracker.incrementTimedLockoutForUser(userId);
+        }
+
+        return lockoutMode;
     }
 
     protected long getStartTimeMs() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 89e08c1..82d5d4d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -19,9 +19,9 @@
 import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
 
 import android.annotation.IntDef;
-import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.WorkerThread;
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -38,7 +38,6 @@
 import com.android.modules.expresslog.Counter;
 import com.android.server.biometrics.BiometricSchedulerProto;
 import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.io.PrintWriter;
@@ -65,9 +64,8 @@
  * @param <T> Hal instance for starting the user.
  * @param <U> Session associated with the current user id.
  *
- * TODO: (b/304604965) Update thread annotation when FLAGS_DE_HIDL is removed.
  */
-@MainThread
+@WorkerThread
 public class BiometricScheduler<T, U> {
 
     private static final String TAG = "BiometricScheduler";
@@ -176,7 +174,7 @@
                     Slog.w(TAG, "operation is already null or different (reset?): "
                             + mCurrentOperation);
                 }
-                startNextOperationIfIdle();
+                checkCurrentUserAndStartNextOperation();
             });
         }
     }
@@ -219,7 +217,7 @@
                 mRecentOperations.add(mCurrentOperation.getProtoEnum());
                 mCurrentOperation = null;
                 mTotalOperationsHandled++;
-                startNextOperationIfIdle();
+                checkCurrentUserAndStartNextOperation();
             });
         }
     };
@@ -304,15 +302,7 @@
         return mInternalCallback;
     }
 
-    protected void startNextOperationIfIdle() {
-        if (Flags.deHidl()) {
-            startNextOperation();
-        } else {
-            startNextOperationIfIdleLegacy();
-        }
-    }
-
-    protected void startNextOperation() {
+    protected void checkCurrentUserAndStartNextOperation() {
         if (mCurrentOperation != null) {
             Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
             return;
@@ -326,7 +316,7 @@
         final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
 
         if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
-            startNextOperationIfIdleLegacy();
+            startNextOperationIfIdle();
         } else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) {
             final BaseClientMonitor startClient =
                     mUserSwitchProvider.getStartUserClient(nextUserId);
@@ -357,7 +347,7 @@
         }
     }
 
-    protected void startNextOperationIfIdleLegacy() {
+    protected void startNextOperationIfIdle() {
         if (mCurrentOperation != null) {
             Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
             return;
@@ -422,7 +412,7 @@
                 // run these. A single request from the manager layer to the service layer may
                 // actually be multiple operations (i.e. updateActiveUser + authenticate).
                 mCurrentOperation = null;
-                startNextOperationIfIdle();
+                checkCurrentUserAndStartNextOperation();
             }
         } else {
             try {
@@ -459,7 +449,7 @@
         } else {
             Slog.e(TAG, "[Unable To Start] Prepared client: " + mCurrentOperation);
             mCurrentOperation = null;
-            startNextOperationIfIdle();
+            checkCurrentUserAndStartNextOperation();
         }
     }
 
@@ -504,7 +494,7 @@
             Slog.d(TAG, "[Cancelling Interruptable]: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
-            startNextOperationIfIdle();
+            checkCurrentUserAndStartNextOperation();
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
deleted file mode 100644
index 7ca10e3..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.IBiometricService;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-
-/**
- * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
- * TODO (b/304604965): Remove class when Flags.FLAG_DE_HIDL is removed.
- *
- * @param <T> Hal instance for starting the user.
- * @param <U> Session associated with the current user id.
- */
-public class UserAwareBiometricScheduler<T, U> extends BiometricScheduler<T, U> {
-
-    private static final String TAG = "UaBiometricScheduler";
-
-    /**
-     * Interface to retrieve the owner's notion of the current userId. Note that even though
-     * the scheduler can determine this based on its history of processed clients, we should still
-     * query the owner since it may be cleared due to things like HAL death, etc.
-     */
-    public interface CurrentUserRetriever {
-        int getCurrentUserId();
-    }
-
-    public interface UserSwitchCallback {
-        @NonNull StopUserClient<?> getStopUserClient(int userId);
-        @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId);
-    }
-
-    @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
-    @NonNull private final UserSwitchCallback mUserSwitchCallback;
-    @Nullable private StopUserClient<?> mStopUserClient;
-
-    private class ClientFinishedCallback implements ClientMonitorCallback {
-        @NonNull private final BaseClientMonitor mOwner;
-
-        ClientFinishedCallback(@NonNull BaseClientMonitor owner) {
-            mOwner = owner;
-        }
-
-        @Override
-        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
-            mHandler.post(() -> {
-                Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
-
-                // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
-                // for that the queue will wait indefinitely until the field is cleared.
-                if (clientMonitor instanceof StopUserClient<?>) {
-                    if (!success) {
-                        Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
-                                + "Clearing mStopUserClient");
-                    }
-                    mStopUserClient = null;
-                }
-                if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
-                    mCurrentOperation = null;
-                } else {
-                    // can happen if the hal dies and is usually okay
-                    // do not unset the current operation that may be newer
-                    Slog.w(TAG, "operation is already null or different (reset?): "
-                            + mCurrentOperation);
-                }
-                startNextOperationIfIdle();
-            });
-        }
-    }
-
-    @VisibleForTesting
-    public UserAwareBiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
-            @SensorType int sensorType,
-            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull IBiometricService biometricService,
-            @NonNull CurrentUserRetriever currentUserRetriever,
-            @NonNull UserSwitchCallback userSwitchCallback) {
-        super(handler, sensorType, gestureAvailabilityDispatcher, biometricService,
-                LOG_NUM_RECENT_OPERATIONS);
-
-        mCurrentUserRetriever = currentUserRetriever;
-        mUserSwitchCallback = userSwitchCallback;
-    }
-
-    public UserAwareBiometricScheduler(@NonNull String tag,
-            @SensorType int sensorType,
-            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull CurrentUserRetriever currentUserRetriever,
-            @NonNull UserSwitchCallback userSwitchCallback) {
-        this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
-                IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
-                currentUserRetriever, userSwitchCallback);
-    }
-
-    @Override
-    protected void startNextOperationIfIdle() {
-        if (mCurrentOperation != null) {
-            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
-            return;
-        }
-        if (mPendingOperations.isEmpty()) {
-            Slog.d(TAG, "No operations, returning to idle");
-            return;
-        }
-
-        final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
-        final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
-
-        if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
-            super.startNextOperationIfIdle();
-        } else if (currentUserId == UserHandle.USER_NULL) {
-            final BaseClientMonitor startClient =
-                    mUserSwitchCallback.getStartUserClient(nextUserId);
-            final ClientFinishedCallback finishedCallback =
-                    new ClientFinishedCallback(startClient);
-
-            Slog.d(TAG, "[Starting User] " + startClient);
-            mCurrentOperation = new BiometricSchedulerOperation(
-                    startClient, finishedCallback, STATE_STARTED);
-            startClient.start(finishedCallback);
-        } else {
-            if (mStopUserClient != null) {
-                Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
-            } else {
-                mStopUserClient = mUserSwitchCallback
-                        .getStopUserClient(currentUserId);
-                final ClientFinishedCallback finishedCallback =
-                        new ClientFinishedCallback(mStopUserClient);
-
-                Slog.d(TAG, "[Stopping User] current: " + currentUserId
-                        + ", next: " + nextUserId + ". " + mStopUserClient);
-                mCurrentOperation = new BiometricSchedulerOperation(
-                        mStopUserClient, finishedCallback, STATE_STARTED);
-                mStopUserClient.start(finishedCallback);
-            }
-        }
-    }
-
-    @Override
-    public void onUserStopped() {
-        if (mStopUserClient == null) {
-            Slog.e(TAG, "Unexpected onUserStopped");
-            return;
-        }
-
-        Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
-        mStopUserClient.onUserStopped();
-        mStopUserClient = null;
-    }
-
-    @VisibleForTesting
-    @Nullable public StopUserClient<?> getStopUserClient() {
-        return mStopUserClient;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index a946af8..bd6d593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -72,9 +72,6 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
-import com.android.server.biometrics.sensors.face.hidl.Face10;
-
-import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -664,60 +661,11 @@
             provider.second.scheduleGetFeature(provider.first, token, userId, feature,
                     new ClientMonitorCallbackConverter(receiver), opPackageName);
         }
-        @NonNull
-        private List<ServiceProvider> getHidlProviders(
-                @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-            final List<ServiceProvider> providers = new ArrayList<>();
-
-            for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
-                providers.add(
-                        Face10.newInstance(getContext(), mBiometricStateCallback,
-                                mAuthenticationStateListeners, hidlSensor,
-                                mLockoutResetDispatcher));
-            }
-
-            return providers;
-        }
-
-        @NonNull
-        private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
-            final List<ServiceProvider> providers = new ArrayList<>();
-
-            for (String instance : instances) {
-                final FaceProvider provider = mFaceProvider.apply(instance);
-                Slog.i(TAG, "Adding AIDL provider: " + instance);
-                providers.add(provider);
-            }
-
-            return providers;
-        }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public void registerAuthenticators(
-                @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-            super.registerAuthenticators_enforcePermission();
-
-            mRegistry.registerAll(() -> {
-                List<String> aidlSensors = new ArrayList<>();
-                final String[] instances = mAidlInstanceNameSupplier.get();
-                if (instances != null) {
-                    aidlSensors.addAll(Lists.newArrayList(instances));
-                }
-
-                final Pair<List<FaceSensorPropertiesInternal>, List<String>>
-                        filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
-                final List<ServiceProvider> providers = new ArrayList<>();
-                providers.addAll(getHidlProviders(filteredInstances.first));
-                providers.addAll(getAidlProviders(filteredInstances.second));
-                return providers;
-            });
-        }
-
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
-        public void registerAuthenticatorsLegacy(
                 FaceSensorConfigurations faceSensorConfigurations) {
-            super.registerAuthenticatorsLegacy_enforcePermission();
+            super.registerAuthenticators_enforcePermission();
 
             if (!faceSensorConfigurations.hasSensorConfigurations()) {
                 Slog.d(TAG, "No face sensors to register.");
@@ -771,40 +719,6 @@
                     .getSensorPropForInstance(finalSensorInstance));
         }
 
-        private Pair<List<FaceSensorPropertiesInternal>, List<String>>
-                filterAvailableHalInstances(
-                @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
-                @NonNull List<String> aidlInstances) {
-            if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
-                return new Pair(hidlInstances, aidlInstances);
-            }
-
-            if (Flags.faceVhalFeature()) {
-                Slog.i(TAG, "Face VHAL feature is on");
-            } else {
-                Slog.i(TAG, "Face VHAL feature is off");
-            }
-
-            final int virtualAt = aidlInstances.indexOf("virtual");
-            if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
-                if (virtualAt != -1) {
-                    //only virtual instance should be returned
-                    Slog.i(TAG, "virtual hal is used");
-                    return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
-                } else {
-                    Slog.e(TAG, "Could not find virtual interface while it is enabled");
-                    return new Pair(hidlInstances, aidlInstances);
-                }
-            } else {
-                //remove virtual instance
-                aidlInstances = new ArrayList<>(aidlInstances);
-                if (virtualAt != -1) {
-                    aidlInstances.remove(virtualAt);
-                }
-                return new Pair(hidlInstances, aidlInstances);
-            }
-        }
-
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFaceAuthenticatorsRegisteredCallback callback) {
@@ -883,17 +797,13 @@
             return null;
         };
 
-        if (Flags.deHidl()) {
-            mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
-                    ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
-                            getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
-                            filteredSensorProps.second,
-                            filteredSensorProps.first, mLockoutResetDispatcher,
-                            BiometricContext.getInstance(getContext()),
-                            resetLockoutRequiresChallenge));
-        } else {
-            mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
-        }
+        mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+                ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+                        getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
+                        filteredSensorProps.second,
+                        filteredSensorProps.first, mLockoutResetDispatcher,
+                        BiometricContext.getInstance(getContext()),
+                        resetLockoutRequiresChallenge));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 098be21..cf677d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,7 +27,6 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.util.Slog;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -53,16 +52,6 @@
     /**
      * Interface to send results to the AidlResponseHandler's owner.
      */
-    public interface HardwareUnavailableCallback {
-        /**
-         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-         */
-        void onHardwareUnavailable();
-    }
-
-    /**
-     * Interface to send results to the AidlResponseHandler's owner.
-     */
     public interface AidlResponseHandlerCallback {
         /**
          * Invoked when enrollment is successful.
@@ -90,8 +79,6 @@
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
-    private final HardwareUnavailableCallback mHardwareUnavailableCallback;
-    @NonNull
     private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     public AidlResponseHandler(@NonNull Context context,
@@ -99,24 +86,6 @@
             @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
-            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
-        this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
-                authSessionCoordinator, hardwareUnavailableCallback,
-                new AidlResponseHandlerCallback() {
-                    @Override
-                    public void onEnrollSuccess() {}
-
-                    @Override
-                    public void onHardwareUnavailable() {}
-                });
-    }
-
-    public AidlResponseHandler(@NonNull Context context,
-            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
-            @NonNull LockoutTracker lockoutTracker,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull AuthSessionCoordinator authSessionCoordinator,
-            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
             @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
         mContext = context;
         mScheduler = scheduler;
@@ -125,7 +94,6 @@
         mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
-        mHardwareUnavailableCallback = hardwareUnavailableCallback;
         mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
     }
 
@@ -185,11 +153,7 @@
         handleResponse(ErrorConsumer.class, (c) -> {
             c.onError(error, vendorCode);
             if (error == Error.HW_UNAVAILABLE) {
-                if (Flags.deHidl()) {
-                    mAidlResponseHandlerCallback.onHardwareUnavailable();
-                } else {
-                    mHardwareUnavailableCallback.onHardwareUnavailable();
-                }
+                mAidlResponseHandlerCallback.onHardwareUnavailable();
             }
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 415d294..c43c7d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
@@ -39,7 +38,6 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -70,8 +68,6 @@
     private final UsageStats mUsageStats;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
-    @Nullable
-    private final NotificationManager mNotificationManager;
     private final int[] mBiometricPromptIgnoreList;
     private final int[] mBiometricPromptIgnoreListVendor;
     private final int[] mKeyguardIgnoreList;
@@ -123,7 +119,6 @@
                 biometricStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
-        mNotificationManager = context.getSystemService(NotificationManager.class);
         mSensorPrivacyManager = sensorPrivacyManager;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
         mAuthenticationStateListeners = authenticationStateListeners;
@@ -163,11 +158,7 @@
                         0 /* vendorCode */);
                 mCallback.onClientFinished(this, false /* success */);
             } else {
-                if (Flags.deHidl()) {
-                    startAuthenticate();
-                } else {
-                    mCancellationSignal = doAuthenticate();
-                }
+                doAuthenticate();
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -176,27 +167,7 @@
         }
     }
 
-    private ICancellationSignal doAuthenticate() throws RemoteException {
-        final AidlSession session = getFreshDaemon();
-
-        if (session.hasContextMethods()) {
-            final OperationContextExt opContext = getOperationContext();
-            final ICancellationSignal cancel = session.getSession().authenticateWithContext(
-                    mOperationId, opContext.toAidlContext(getOptions()));
-            getBiometricContext().subscribe(opContext, ctx -> {
-                try {
-                    session.getSession().onContextChanged(ctx);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify context changed", e);
-                }
-            });
-            return cancel;
-        } else {
-            return session.getSession().authenticate(mOperationId);
-        }
-    }
-
-    private void startAuthenticate() throws RemoteException {
+    private void doAuthenticate() throws RemoteException {
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 5ddddda..dcd94896 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -29,7 +29,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -112,38 +111,14 @@
         }
 
         try {
-            if (Flags.deHidl()) {
-                startDetect();
-            } else {
-                mCancellationSignal = doDetectInteraction();
-            }
+            doDetectInteraction();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting face detect", e);
             mCallback.onClientFinished(this, false /* success */);
         }
     }
 
-    private ICancellationSignal doDetectInteraction() throws RemoteException {
-        final AidlSession session = getFreshDaemon();
-
-        if (session.hasContextMethods()) {
-            final OperationContextExt opContext = getOperationContext();
-            final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
-                    opContext.toAidlContext(mOptions));
-            getBiometricContext().subscribe(opContext, ctx -> {
-                try {
-                    session.getSession().onContextChanged(ctx);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify context changed", e);
-                }
-            });
-            return cancel;
-        } else {
-            return session.getSession().detectInteraction();
-        }
-    }
-
-    private void startDetect() throws RemoteException {
+    private void doDetectInteraction() throws RemoteException {
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 781e3f4..73e8ece 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -36,7 +36,6 @@
 import android.view.Surface;
 
 import com.android.internal.R;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -193,11 +192,7 @@
                 features[i] = featureList.get(i);
             }
 
-            if (Flags.deHidl()) {
-                startEnroll(features);
-            } else {
-                mCancellationSignal = doEnroll(features);
-            }
+            doEnroll(features);
         } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -205,43 +200,7 @@
         }
     }
 
-    private ICancellationSignal doEnroll(byte[] features) throws RemoteException {
-        final AidlSession session = getFreshDaemon();
-        final HardwareAuthToken hat =
-                HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
-
-        if (session.hasContextMethods()) {
-            final OperationContextExt opContext = getOperationContext();
-            ICancellationSignal cancel;
-            if (session.supportsFaceEnrollOptions()) {
-                FaceEnrollOptions options = new FaceEnrollOptions();
-                options.hardwareAuthToken = hat;
-                options.enrollmentType = EnrollmentType.DEFAULT;
-                options.features = features;
-                options.nativeHandlePreview = null;
-                options.context = opContext.toAidlContext();
-                options.surfacePreview = mPreviewSurface;
-                cancel = session.getSession().enrollWithOptions(options);
-            } else {
-                cancel = session.getSession().enrollWithContext(
-                        hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle,
-                        opContext.toAidlContext());
-            }
-            getBiometricContext().subscribe(opContext, ctx -> {
-                try {
-                    session.getSession().onContextChanged(ctx);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify context changed", e);
-                }
-            });
-            return cancel;
-        } else {
-            return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
-                    mHwPreviewHandle);
-        }
-    }
-
-    private void startEnroll(byte[] features) throws RemoteException {
+    private void doEnroll(byte[] features) throws RemoteException {
         final AidlSession session = getFreshDaemon();
         final HardwareAuthToken hat =
                 HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 11db183..75b4fd3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -27,11 +27,9 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.common.ComponentInfo;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
@@ -43,7 +41,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -56,7 +53,6 @@
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -193,11 +189,7 @@
         mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
-        if (Flags.deHidl()) {
-            mHandler = biometricHandlerProvider.getFaceHandler();
-        } else {
-            mHandler = new Handler(Looper.getMainLooper());
-        }
+        mHandler = biometricHandlerProvider.getFaceHandler();
         mUsageStats = new UsageStats(context);
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -223,50 +215,15 @@
     }
 
     private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
-        if (Flags.deHidl()) {
-            if (resetLockoutRequiresChallenge) {
-                Slog.d(getTag(), "Adding HIDL configs");
-                for (SensorProps prop : props) {
-                    addHidlSensors(prop, resetLockoutRequiresChallenge);
-                }
-            } else {
-                Slog.d(getTag(), "Adding AIDL configs");
-                for (SensorProps prop : props) {
-                    addAidlSensors(prop, resetLockoutRequiresChallenge);
-                }
+        if (resetLockoutRequiresChallenge) {
+            Slog.d(getTag(), "Adding HIDL configs");
+            for (SensorProps prop : props) {
+                addHidlSensors(prop, resetLockoutRequiresChallenge);
             }
         } else {
+            Slog.d(getTag(), "Adding AIDL configs");
             for (SensorProps prop : props) {
-                final int sensorId = prop.commonProps.sensorId;
-
-                final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-                if (prop.commonProps.componentInfo != null) {
-                    for (ComponentInfo info : prop.commonProps.componentInfo) {
-                        componentInfo.add(new ComponentInfoInternal(info.componentId,
-                                info.hardwareVersion, info.firmwareVersion, info.serialNumber,
-                                info.softwareVersion));
-                    }
-                }
-
-                final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                        prop.commonProps.sensorId, prop.commonProps.sensorStrength,
-                        prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
-                        prop.supportsDetectInteraction, prop.halControlsPreview,
-                        false /* resetLockoutRequiresChallenge */);
-                final Sensor sensor = new Sensor(this,
-                        mContext, mHandler, internalProp,
-                        mBiometricContext);
-                sensor.init(mLockoutResetDispatcher, this);
-                final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
-                        sensor.getLazySession().get().getUserId();
-                mFaceSensors.addSensor(sensorId, sensor, userId,
-                        new SynchronousUserSwitchObserver() {
-                            @Override
-                            public void onUserSwitching(int newUserId) {
-                                scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
-                            }
-                        });
-                Slog.d(getTag(), "Added: " + internalProp);
+                addAidlSensors(prop, resetLockoutRequiresChallenge);
             }
         }
     }
@@ -477,12 +434,7 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        if (Flags.deHidl()) {
-            return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
-        } else {
-            return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
-                    Utils.getCurrentStrength(sensorId));
-        }
+        return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
     }
 
     @Override
@@ -492,11 +444,7 @@
 
     @Override
     public boolean isHardwareDetected(int sensorId) {
-        if (Flags.deHidl()) {
-            return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
-        } else {
-            return hasHalInstance();
-        }
+        return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
     }
 
     @Override
@@ -549,23 +497,7 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent, options);
-            if (Flags.deHidl()) {
-                scheduleForSensor(sensorId, client, mBiometricStateCallback);
-            } else {
-                scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
-                        mBiometricStateCallback, new ClientMonitorCallback() {
-                            @Override
-                            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                                    boolean success) {
-                                ClientMonitorCallback.super.onClientFinished(clientMonitor,
-                                        success);
-                                if (success) {
-                                    scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                                    scheduleInvalidationRequest(sensorId, userId);
-                                }
-                            }
-                        }));
-            }
+            scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
         return id;
     }
@@ -614,12 +546,8 @@
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
-            final LockoutTracker lockoutTracker;
-            if (Flags.deHidl()) {
-                lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
-            } else {
-                lockoutTracker = null;
-            }
+            final LockoutTracker lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(
+                    true /* forAuth */);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
@@ -634,29 +562,18 @@
                 @Override
                 public void onClientStarted(
                          BaseClientMonitor clientMonitor) {
-                    if (Flags.deHidl()) {
-                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
-                                mAuthSessionCoordinator.authStartedFor(userId, sensorId,
-                                        requestId));
-                    } else {
-                        mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
-                    }
+                    mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                            mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId));
                 }
 
                 @Override
                 public void onClientFinished(
                         BaseClientMonitor clientMonitor,
                         boolean success) {
-                    if (Flags.deHidl()) {
-                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
-                                mAuthSessionCoordinator.authEndedFor(userId,
-                                        Utils.getCurrentStrength(sensorId), sensorId, requestId,
-                                        client.wasAuthSuccessful()));
-                    } else {
-                        mAuthSessionCoordinator.authEndedFor(userId,
-                                Utils.getCurrentStrength(sensorId),
-                                sensorId, requestId, client.wasAuthSuccessful());
-                    }
+                    mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                            mAuthSessionCoordinator.authEndedFor(userId,
+                                    Utils.getCurrentStrength(sensorId), sensorId, requestId,
+                                    client.wasAuthSuccessful()));
                 }
             });
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 635e79a..6b99493 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -42,7 +42,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -57,7 +56,6 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
@@ -89,7 +87,6 @@
     @Nullable AidlSession mCurrentSession;
     @NonNull BiometricContext mBiometricContext;
 
-
     Sensor(@NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
             @NonNull BiometricContext biometricContext) {
@@ -116,11 +113,7 @@
      */
     public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull FaceProvider provider) {
-        if (Flags.deHidl()) {
-            setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
-        } else {
-            setScheduler(getUserAwareBiometricSchedulerForInit(lockoutResetDispatcher, provider));
-        }
+        setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
         mLockoutTracker = new LockoutCache();
     }
@@ -149,8 +142,7 @@
                         final AidlResponseHandler resultController = new AidlResponseHandler(
                                 mContext, mScheduler, sensorId, newUserId,
                                 mLockoutTracker, lockoutResetDispatcher,
-                                mBiometricContext.getAuthSessionCoordinator(), () -> {
-                        },
+                                mBiometricContext.getAuthSessionCoordinator(),
                                 new AidlResponseHandler.AidlResponseHandlerCallback() {
                                     @Override
                                     public void onEnrollSuccess() {
@@ -173,40 +165,6 @@
                 });
     }
 
-    private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit(
-            LockoutResetDispatcher lockoutResetDispatcher,
-            FaceProvider provider) {
-        return new UserAwareBiometricScheduler<>(TAG,
-                BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
-                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
-                new UserAwareBiometricScheduler.UserSwitchCallback() {
-                    @NonNull
-                    @Override
-                    public StopUserClient<ISession> getStopUserClient(int userId) {
-                        return new FaceStopUserClient(mContext,
-                                () -> mLazySession.get().getSession(), mToken, userId,
-                                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
-                                mBiometricContext, () -> mCurrentSession = null);
-                    }
-
-                    @NonNull
-                    @Override
-                    public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
-                        final int sensorId = mSensorProperties.sensorId;
-                        final AidlResponseHandler resultController = new AidlResponseHandler(
-                                mContext, mScheduler, sensorId, newUserId,
-                                mLockoutTracker, lockoutResetDispatcher,
-                                mBiometricContext.getAuthSessionCoordinator(), () -> {
-                                    Slog.e(TAG, "Face sensor hardware unavailable.");
-                                    mCurrentSession = null;
-                                });
-
-                        return Sensor.this.getStartUserClient(resultController, sensorId,
-                                newUserId, provider);
-                    }
-                });
-    }
-
     private FaceStartUserClient getStartUserClient(@NonNull AidlResponseHandler resultController,
             int sensorId, int newUserId, @NonNull FaceProvider provider) {
         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
deleted file mode 100644
index 0e2367a..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.face.Face;
-import android.hardware.face.FaceAuthenticationFrame;
-import android.hardware.face.FaceEnrollFrame;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.face.FaceUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-public class BiometricTestSessionImpl extends ITestSession.Stub {
-    private static final String TAG = "BiometricTestSessionImpl";
-
-    @NonNull private final Context mContext;
-    private final int mSensorId;
-    @NonNull private final ITestSessionCallback mCallback;
-    @NonNull private final Face10 mFace10;
-    @NonNull private final Face10.HalResultController mHalResultController;
-    @NonNull private final Set<Integer> mEnrollmentIds;
-    @NonNull private final Random mRandom;
-
-
-    private final IFaceServiceReceiver mReceiver = new IFaceServiceReceiver.Stub() {
-        @Override
-        public void onEnrollResult(Face face, int remaining) {
-
-        }
-
-        @Override
-        public void onAcquired(int acquiredInfo, int vendorCode) {
-
-        }
-
-        @Override
-        public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-
-        }
-
-        @Override
-        public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-
-        }
-
-        @Override
-        public void onAuthenticationFailed() {
-
-        }
-
-        @Override
-        public void onError(int error, int vendorCode) {
-
-        }
-
-        @Override
-        public void onRemoved(Face face, int remaining) {
-
-        }
-
-        @Override
-        public void onFeatureSet(boolean success, int feature) {
-
-        }
-
-        @Override
-        public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-
-        }
-
-        @Override
-        public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-
-        }
-
-        @Override
-        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-
-        }
-
-        @Override
-        public void onEnrollmentFrame(FaceEnrollFrame frame) {
-
-        }
-    };
-
-    BiometricTestSessionImpl(@NonNull Context context, int sensorId,
-            @NonNull ITestSessionCallback callback,
-            @NonNull Face10 face10,
-            @NonNull Face10.HalResultController halResultController) {
-        mContext = context;
-        mSensorId = sensorId;
-        mCallback = callback;
-        mFace10 = face10;
-        mHalResultController = halResultController;
-        mEnrollmentIds = new HashSet<>();
-        mRandom = new Random();
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void setTestHalEnabled(boolean enabled) {
-
-        super.setTestHalEnabled_enforcePermission();
-
-        mFace10.setTestHalEnabled(enabled);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void startEnroll(int userId) {
-
-        super.startEnroll_enforcePermission();
-
-        mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
-                null /* previewSurface */, false /* debugConsent */,
-                (new FaceEnrollOptions.Builder()).build());
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void finishEnroll(int userId) {
-
-        super.finishEnroll_enforcePermission();
-
-        int nextRandomId = mRandom.nextInt();
-        while (mEnrollmentIds.contains(nextRandomId)) {
-            nextRandomId = mRandom.nextInt();
-        }
-
-        mEnrollmentIds.add(nextRandomId);
-        mHalResultController.onEnrollResult(0 /* deviceId */,
-                nextRandomId /* faceId */, userId, 0);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void acceptAuthentication(int userId) {
-
-        // Fake authentication with any of the existing fingers
-        super.acceptAuthentication_enforcePermission();
-
-        List<Face> faces = FaceUtils.getLegacyInstance(mSensorId)
-                .getBiometricsForUser(mContext, userId);
-        if (faces.isEmpty()) {
-            Slog.w(TAG, "No faces, returning");
-            return;
-        }
-        final int fid = faces.get(0).getBiometricId();
-        final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
-        mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void rejectAuthentication(int userId) {
-
-        super.rejectAuthentication_enforcePermission();
-
-        mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void notifyAcquired(int userId, int acquireInfo) {
-
-        super.notifyAcquired_enforcePermission();
-
-        mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void notifyError(int userId, int errorCode) {
-
-        super.notifyError_enforcePermission();
-
-        mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void cleanupInternalState(int userId) {
-
-        super.cleanupInternalState_enforcePermission();
-
-        mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                try {
-                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception", e);
-                }
-            }
-
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                try {
-                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception", e);
-                }
-            }
-        });
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
deleted file mode 100644
index 306ddfa..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ /dev/null
@@ -1,1342 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
-import android.hardware.face.Face;
-import android.hardware.face.FaceAuthenticateOptions;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.Looper;
-import android.os.NativeHandle;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
-import com.android.server.biometrics.AuthenticationStatsCollector;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.SensorServiceStateProto;
-import com.android.server.biometrics.SensorStateProto;
-import com.android.server.biometrics.UserStateProto;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
-import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.face.FaceUtils;
-import com.android.server.biometrics.sensors.face.LockoutHalImpl;
-import com.android.server.biometrics.sensors.face.ServiceProvider;
-import com.android.server.biometrics.sensors.face.UsageStats;
-import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
-import com.android.server.biometrics.sensors.face.aidl.AidlSession;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.time.Clock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Supplier;
-
-/**
- * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
- * minor versions.
- */
-public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
-
-    private static final String TAG = "Face10";
-
-    private static final int ENROLL_TIMEOUT_SEC = 75;
-    private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
-    private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS =
-            FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC * 1000;
-    @VisibleForTesting
-    public static Clock sSystemClock = Clock.systemUTC();
-
-    private boolean mTestHalEnabled;
-
-    @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull
-    private final AuthenticationStateListeners mAuthenticationStateListeners;
-    @NonNull private final Context mContext;
-    @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
-    @NonNull private final Handler mHandler;
-    @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
-    @NonNull private final LockoutHalImpl mLockoutTracker;
-    @NonNull private final UsageStats mUsageStats;
-    @NonNull private final Map<Integer, Long> mAuthenticatorIds;
-    @Nullable private IBiometricsFace mDaemon;
-    @NonNull private final HalResultController mHalResultController;
-    @NonNull private final BiometricContext mBiometricContext;
-    @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
-    // for requests that do not use biometric prompt
-    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-    private int mCurrentUserId = UserHandle.USER_NULL;
-    private final int mSensorId;
-    private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
-    private final LockoutResetDispatcher mLockoutResetDispatcher;
-    private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
-    private AidlSession mSession;
-
-    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
-        @Override
-        public void onUserSwitching(int newUserId) {
-            scheduleInternalCleanup(newUserId, null /* callback */);
-            scheduleGetFeature(mSensorId, new Binder(), newUserId,
-                    BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
-                    null, mContext.getOpPackageName());
-        }
-    };
-
-    public static class HalResultController extends IBiometricsFaceClientCallback.Stub {
-        /**
-         * Interface to sends results to the HalResultController's owner.
-         */
-        public interface Callback {
-            /**
-             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-             */
-            void onHardwareUnavailable();
-        }
-
-        private final int mSensorId;
-        @NonNull private final Context mContext;
-        @NonNull private final Handler mHandler;
-        @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
-        @Nullable private Callback mCallback;
-        @NonNull private final LockoutHalImpl mLockoutTracker;
-        @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-
-
-        HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
-                @NonNull LockoutHalImpl lockoutTracker,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-            mSensorId = sensorId;
-            mContext = context;
-            mHandler = handler;
-            mScheduler = scheduler;
-            mLockoutTracker = lockoutTracker;
-            mLockoutResetDispatcher = lockoutResetDispatcher;
-        }
-
-        public void setCallback(@Nullable Callback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
-            mHandler.post(() -> {
-                final CharSequence name = FaceUtils.getLegacyInstance(mSensorId)
-                        .getUniqueName(mContext, userId);
-                final Face face = new Face(name, faceId, deviceId);
-
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceEnrollClient)) {
-                    Slog.e(TAG, "onEnrollResult for non-enroll client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
-                enrollClient.onEnrollResult(face, remaining);
-            });
-        }
-
-        @Override
-        public void onAuthenticated(long deviceId, int faceId, int userId,
-                ArrayList<Byte> token) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final boolean authenticated = faceId != 0;
-                final Face face = new Face("", faceId, deviceId);
-                authenticationConsumer.onAuthenticated(face, authenticated, token);
-            });
-        }
-
-        @Override
-        public void onAcquired(long deviceId, int userId, int acquiredInfo,
-                int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AcquisitionClient)) {
-                    Slog.e(TAG, "onAcquired for non-acquire client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AcquisitionClient<?> acquisitionClient =
-                        (AcquisitionClient<?>) client;
-                acquisitionClient.onAcquired(acquiredInfo, vendorCode);
-            });
-        }
-
-        @Override
-        public void onError(long deviceId, int userId, int error, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                Slog.d(TAG, "handleError"
-                        + ", client: " + (client != null ? client.getOwnerString() : null)
-                        + ", error: " + error
-                        + ", vendorCode: " + vendorCode);
-                if (!(client instanceof ErrorConsumer)) {
-                    Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(
-                            client));
-                    return;
-                }
-
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(error, vendorCode);
-
-                if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
-                    Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
-                    if (mCallback != null) {
-                        mCallback.onHardwareUnavailable();
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof RemovalConsumer)) {
-                    Slog.e(TAG, "onRemoved for non-removal consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-
-                if (!removed.isEmpty()) {
-                    // Convert to old fingerprint-like behavior, where remove() receives
-                    // one removal at a time. This way, remove can share some more common code.
-                    for (int i = 0; i < removed.size(); i++) {
-                        final int id = removed.get(i);
-                        final Face face = new Face("", id, deviceId);
-                        final int remaining = removed.size() - i - 1;
-                        Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
-                        removalConsumer.onRemoved(face, remaining);
-                    }
-                } else {
-                    removalConsumer.onRemoved(null, 0 /* remaining */);
-                }
-
-                Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
-            });
-        }
-
-        @Override
-        public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof EnumerateConsumer)) {
-                    Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
-
-                if (!faceIds.isEmpty()) {
-                    // Convert to old fingerprint-like behavior, where enumerate() receives one
-                    // template at a time. This way, enumerate can share some more common code.
-                    for (int i = 0; i < faceIds.size(); i++) {
-                        final Face face = new Face("", faceIds.get(i), deviceId);
-                        enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
-                    }
-                } else {
-                    // For face, the HIDL contract is to receive an empty list when there are no
-                    // templates enrolled. Send a null identifier since we don't consume them
-                    // anywhere, and send remaining == 0 so this code can be shared with Face@1.1
-                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
-                }
-            });
-        }
-
-        @Override
-        public void onLockoutChanged(long duration) {
-            mHandler.post(() -> {
-                Slog.d(TAG, "onLockoutChanged: " + duration);
-                final @LockoutTracker.LockoutMode int lockoutMode;
-                if (duration == 0) {
-                    lockoutMode = LockoutTracker.LOCKOUT_NONE;
-                } else if (duration == -1 || duration == Long.MAX_VALUE) {
-                    lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
-                } else {
-                    lockoutMode = LockoutTracker.LOCKOUT_TIMED;
-                }
-
-                mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
-
-                if (duration == 0) {
-                    mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
-                }
-            });
-        }
-    }
-
-    @VisibleForTesting
-    Face10(@NonNull Context context,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @NonNull FaceSensorPropertiesInternal sensorProps,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull Handler handler,
-            @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
-            @NonNull BiometricContext biometricContext) {
-        mSensorProperties = sensorProps;
-        mContext = context;
-        mBiometricStateCallback = biometricStateCallback;
-        mAuthenticationStateListeners = authenticationStateListeners;
-        mSensorId = sensorProps.sensorId;
-        mScheduler = scheduler;
-        mHandler = handler;
-        mBiometricContext = biometricContext;
-        mUsageStats = new UsageStats(context);
-        mAuthenticatorIds = new HashMap<>();
-        mLazyDaemon = Face10.this::getDaemon;
-        mLockoutTracker = new LockoutHalImpl();
-        mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
-                mScheduler, mLockoutTracker, lockoutResetDispatcher);
-        mLockoutResetDispatcher = lockoutResetDispatcher;
-        mHalResultController.setCallback(() -> {
-            mDaemon = null;
-            mCurrentUserId = UserHandle.USER_NULL;
-        });
-
-        AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
-                new AuthenticationStatsBroadcastReceiver(
-                        mContext,
-                        BiometricsProtoEnums.MODALITY_FACE,
-                        (AuthenticationStatsCollector collector) -> {
-                            Slog.d(TAG, "Initializing AuthenticationStatsCollector");
-                            mAuthenticationStatsCollector = collector;
-                        });
-
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to register user switch observer");
-        }
-    }
-
-    public static Face10 newInstance(@NonNull Context context,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @NonNull FaceSensorPropertiesInternal sensorProps,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-        final Handler handler = new Handler(Looper.getMainLooper());
-        return new Face10(context, biometricStateCallback, authenticationStateListeners,
-                sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
-                        BiometricScheduler.SENSOR_TYPE_FACE,
-                        null /* gestureAvailabilityTracker */),
-                BiometricContext.getInstance(context));
-    }
-
-    @Override
-    public void serviceDied(long cookie) {
-        Slog.e(TAG, "HAL died");
-        mHandler.post(() -> {
-            PerformanceTracker.getInstanceForSensorId(mSensorId)
-                    .incrementHALDeathCount();
-            mDaemon = null;
-            mCurrentUserId = UserHandle.USER_NULL;
-
-            final BaseClientMonitor client = mScheduler.getCurrentClient();
-            if (client instanceof ErrorConsumer) {
-                Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                        0 /* vendorCode */);
-
-                FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
-                        BiometricsProtoEnums.MODALITY_FACE,
-                        BiometricsProtoEnums.ISSUE_HAL_DEATH,
-                        -1 /* sensorId */);
-            }
-
-            mScheduler.recordCrashState();
-            mScheduler.reset();
-        });
-    }
-
-    public int getCurrentUserId() {
-        return mCurrentUserId;
-    }
-
-    synchronized AidlSession getSession() {
-        if (mDaemon != null && mSession != null) {
-            return mSession;
-        } else {
-            return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId,
-                    new AidlResponseHandler(mContext, mScheduler, mSensorId,
-                            mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
-                            new AuthSessionCoordinator(), () -> {
-                        mDaemon = null;
-                        mCurrentUserId = UserHandle.USER_NULL;
-                    }));
-        }
-    }
-
-    private synchronized IBiometricsFace getDaemon() {
-        if (mTestHalEnabled) {
-            final TestHal testHal = new TestHal(mContext, mSensorId);
-            testHal.setCallback(mHalResultController);
-            return testHal;
-        }
-
-        if (mDaemon != null) {
-            return mDaemon;
-        }
-
-        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
-                + mScheduler.getCurrentClient());
-
-        try {
-            mDaemon = IBiometricsFace.getService();
-        } catch (java.util.NoSuchElementException e) {
-            // Service doesn't exist or cannot be opened.
-            Slog.w(TAG, "NoSuchElementException", e);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to get face HAL", e);
-        }
-
-        if (mDaemon == null) {
-            Slog.w(TAG, "Face HAL not available");
-            return null;
-        }
-
-        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
-        // HAL ID for these HIDL versions are only used to determine if callbacks have been
-        // successfully set.
-        long halId = 0;
-        try {
-            halId = mDaemon.setCallback(mHalResultController).value;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to set callback for face HAL", e);
-            mDaemon = null;
-        }
-
-        Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
-        if (halId != 0) {
-            scheduleLoadAuthenticatorIds();
-            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
-            scheduleGetFeature(mSensorId, new Binder(),
-                    ActivityManager.getCurrentUser(),
-                    BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
-                    mContext.getOpPackageName());
-        } else {
-            Slog.e(TAG, "Unable to set callback");
-            mDaemon = null;
-        }
-
-        return mDaemon;
-    }
-
-    @Override
-    public boolean containsSensor(int sensorId) {
-        return mSensorId == sensorId;
-    }
-
-    @Override
-    @NonNull
-    public List<FaceSensorPropertiesInternal> getSensorProperties() {
-        final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
-        properties.add(mSensorProperties);
-        return properties;
-    }
-
-    @NonNull
-    @Override
-    public FaceSensorPropertiesInternal getSensorProperties(int sensorId) {
-        return mSensorProperties;
-    }
-
-    @Override
-    @NonNull
-    public List<Face> getEnrolledFaces(int sensorId, int userId) {
-        return FaceUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
-    }
-
-    @Override
-    public boolean hasEnrollments(int sensorId, int userId) {
-        return !getEnrolledFaces(sensorId, userId).isEmpty();
-    }
-
-    @Override
-    @LockoutTracker.LockoutMode
-    public int getLockoutModeForUser(int sensorId, int userId) {
-        return mLockoutTracker.getLockoutModeForUser(userId);
-    }
-
-    @Override
-    public long getAuthenticatorId(int sensorId, int userId) {
-        return mAuthenticatorIds.getOrDefault(userId, 0L);
-    }
-
-    @Override
-    public boolean isHardwareDetected(int sensorId) {
-        return getDaemon() != null;
-    }
-
-    private boolean isGeneratedChallengeCacheValid() {
-        return mGeneratedChallengeCache != null
-                && sSystemClock.millis() - mGeneratedChallengeCache.getCreatedAt()
-                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
-    }
-
-    private void incrementChallengeCount() {
-        mGeneratedChallengeCount.add(0, sSystemClock.millis());
-    }
-
-    private int decrementChallengeCount() {
-        final long now = sSystemClock.millis();
-        // ignore values that are old in case generate/revoke calls are not matched
-        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
-        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
-        if (!mGeneratedChallengeCount.isEmpty()) {
-            mGeneratedChallengeCount.remove(0);
-        }
-        return mGeneratedChallengeCount.size();
-    }
-
-    /**
-     * {@link IBiometricsFace} only supports a single in-flight challenge but there are cases where
-     * two callers both need challenges (e.g. resetLockout right before enrollment).
-     */
-    @Override
-    public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            if (Flags.deHidl()) {
-                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
-            } else {
-                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient(
-                        mContext, this::getSession, token,
-                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                        mBiometricContext);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                if (client != clientMonitor) {
-                    Slog.e(TAG,
-                            "scheduleGenerateChallenge onClientStarted, mismatched client."
-                                    + " Expecting: " + client + ", received: "
-                                    + clientMonitor);
-                }
-            }
-        });
-    }
-
-    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        incrementChallengeCount();
-        if (isGeneratedChallengeCacheValid()) {
-            Slog.d(TAG, "Current challenge is cached and will be reused");
-            mGeneratedChallengeCache.reuseResult(receiver);
-            return;
-        }
-
-        final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
-                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, sSystemClock.millis());
-        mGeneratedChallengeCache = client;
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                if (client != clientMonitor) {
-                    Slog.e(TAG,
-                            "scheduleGenerateChallenge onClientStarted, mismatched client."
-                                    + " Expecting: " + client + ", received: "
-                                    + clientMonitor);
-                }
-            }
-        });
-    }
-
-    @Override
-    public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
-            @NonNull String opPackageName, long challenge) {
-        mHandler.post(() -> {
-            if (Flags.deHidl()) {
-                scheduleRevokeChallengeAidl(userId, token, opPackageName);
-            } else {
-                scheduleRevokeChallengeHidl(userId, token, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
-            @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient
-                client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient(
-                        mContext, this::getSession, token, userId, opPackageName, mSensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector), mBiometricContext, 0L);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                if (client != clientMonitor) {
-                    Slog.e(TAG,
-                            "scheduleRevokeChallenge, mismatched client." + "Expecting: "
-                                    + client + ", received: " + clientMonitor);
-                }
-            }
-        });
-    }
-
-    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
-            @NonNull String opPackageName) {
-        final boolean shouldRevoke = decrementChallengeCount() == 0;
-        if (!shouldRevoke) {
-            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
-                    + mGeneratedChallengeCount);
-            return;
-        }
-
-        Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
-        mGeneratedChallengeCache = null;
-        final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                mLazyDaemon, token, userId, opPackageName, mSensorId,
-                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                if (client != clientMonitor) {
-                    Slog.e(TAG,
-                            "scheduleRevokeChallenge, mismatched client." + "Expecting: "
-                                    + client + ", received: " + clientMonitor);
-                }
-            }
-        });
-    }
-
-    @Override
-    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
-            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
-            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
-            @Nullable Surface previewSurface, boolean debugConsent,
-            @NonNull FaceEnrollOptions options) {
-        final long id = mRequestCounter.incrementAndGet();
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-            if (Flags.deHidl()) {
-                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
-                        opPackageName, disabledFeatures, previewSurface, id, options);
-            } else {
-                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
-                        opPackageName, disabledFeatures, previewSurface, id, options);
-            }
-        });
-        return id;
-    }
-
-    private void scheduleEnrollAidl(@NonNull IBinder token,
-            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
-            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
-            @Nullable Surface previewSurface, long id,
-            @NonNull FaceEnrollOptions options) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
-                        mContext, this::getSession, token,
-                        new ClientMonitorCallbackConverter(receiver), userId,
-                        hardwareAuthToken, opPackageName, id,
-                        FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
-                        ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector), mBiometricContext,
-                        mContext.getResources().getInteger(
-                                com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
-                        false, options);
-
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                mBiometricStateCallback.onClientStarted(clientMonitor);
-            }
-
-            @Override
-            public void onBiometricAction(int action) {
-                mBiometricStateCallback.onBiometricAction(action);
-            }
-
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
-                }
-            }
-        });
-    }
-
-    private void scheduleEnrollHidl(@NonNull IBinder token,
-            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
-            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
-            @Nullable Surface previewSurface, long id, FaceEnrollOptions options) {
-            final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
-                    ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, options);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    mBiometricStateCallback.onClientStarted(clientMonitor);
-                }
-
-                @Override
-                public void onBiometricAction(int action) {
-                    mBiometricStateCallback.onBiometricAction(action);
-                }
-
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                    if (success) {
-                        // Update authenticatorIds
-                        scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
-                    }
-                }
-            });
-    }
-
-    @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
-    }
-
-    @Override
-    public long scheduleFaceDetect(@NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull FaceAuthenticateOptions options, int statsClient) {
-        throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
-                + "forget to check the supportsFaceDetection flag?");
-    }
-
-    @Override
-    public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
-        throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
-                + "forget to check the supportsFaceDetection flag?");
-    }
-
-    @Override
-    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
-            int statsClient, boolean allowBackgroundAuthentication) {
-        mHandler.post(() -> {
-            final int userId = options.getUserId();
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
-            if (Flags.deHidl()) {
-                scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId,
-                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
-            } else {
-                scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId,
-                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
-            }
-        });
-    }
-
-    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
-            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient
-                client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient(
-                        mContext, this::getSession, token, requestId, receiver, operationId,
-                        restricted, options, cookie, false /* requireConfirmation */,
-                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                                mAuthenticationStatsCollector), mBiometricContext,
-                        isStrongBiometric, mUsageStats, mLockoutTracker,
-                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
-                        mAuthenticationStateListeners);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
-            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
-        final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
-                mLazyDaemon, token, requestId, receiver, operationId, restricted, options,
-                cookie, false /* requireConfirmation */,
-                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                        mAuthenticationStatsCollector), mBiometricContext,
-                isStrongBiometric, mLockoutTracker, mUsageStats,
-                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
-                mAuthenticationStateListeners);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    @Override
-    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication) {
-        final long id = mRequestCounter.incrementAndGet();
-
-        scheduleAuthenticate(token, operationId, cookie, receiver,
-                options, id, restricted, statsClient, allowBackgroundAuthentication);
-
-        return id;
-    }
-
-    @Override
-    public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
-    }
-
-    @Override
-    public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            if (Flags.deHidl()) {
-                scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
-            } else {
-                scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
-            }
-        });
-    }
-
-    @Override
-    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            // For IBiometricsFace@1.0, remove(0) means remove all enrollments
-            if (Flags.deHidl()) {
-                scheduleRemoveAidl(token, userId, receiver, opPackageName, 0);
-            } else {
-                scheduleRemoveHidl(token, userId, receiver, opPackageName, 0);
-            }
-        });
-    }
-
-    private void scheduleRemoveAidl(@NonNull IBinder token, int userId,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient(
-                        mContext, this::getSession, token,
-                        new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId,
-                        opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector), mBiometricContext,
-                        mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    private void scheduleRemoveHidl(@NonNull IBinder token, int userId,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
-        final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
-                new ClientMonitorCallbackConverter(receiver), faceId, userId,
-                opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
-                createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    @Override
-    public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
-        mHandler.post(() -> {
-            if (getEnrolledFaces(sensorId, userId).isEmpty()) {
-                Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
-                return;
-            }
-
-            scheduleUpdateActiveUserWithoutHandler(userId);
-            if (Flags.deHidl()) {
-                scheduleResetLockoutAidl(userId, hardwareAuthToken);
-            } else {
-                scheduleResetLockoutHidl(userId, hardwareAuthToken);
-            }
-        });
-    }
-
-    private void scheduleResetLockoutAidl(int userId,
-            @NonNull byte[] hardwareAuthToken) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient(
-                        mContext, this::getSession, userId, mContext.getOpPackageName(),
-                        mSensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext, hardwareAuthToken, mLockoutTracker,
-                        mLockoutResetDispatcher, mSensorProperties.sensorStrength);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleResetLockoutHidl(int userId,
-            @NonNull byte[] hardwareAuthToken) {
-        final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
-                mLazyDaemon,
-                userId, mContext.getOpPackageName(), mSensorId,
-                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, hardwareAuthToken);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    @Override
-    public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
-            boolean enabled, @NonNull byte[] hardwareAuthToken,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-            if (Flags.deHidl()) {
-                scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
-                        receiver, opPackageName);
-            } else {
-                scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
-                        receiver, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
-            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        final List<Face> faces = getEnrolledFaces(sensorId, userId);
-        if (faces.isEmpty()) {
-            Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
-            return;
-        }
-        final int faceId = faces.get(0).getBiometricId();
-        final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon,
-                token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature,
-                enabled, hardwareAuthToken, faceId);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId,
-            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
-            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient(
-                        mContext, this::getSession, token,
-                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                        feature, enabled, hardwareAuthToken);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-
-    @Override
-    public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
-            @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            if (Flags.deHidl()) {
-                scheduleGetFeatureAidl(token, userId, feature, listener,
-                        opPackageName);
-            } else {
-                scheduleGetFeatureHidl(sensorId, token, userId, feature, listener,
-                        opPackageName);
-            }
-        });
-    }
-
-    private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
-            int feature, @Nullable ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName) {
-        final List<Face> faces = getEnrolledFaces(sensorId, userId);
-        if (faces.isEmpty()) {
-            Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
-            return;
-        }
-
-        final int faceId = faces.get(0).getBiometricId();
-        final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
-                token, listener, userId, opPackageName, mSensorId,
-                BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                if (success
-                        && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
-                    final int settingsValue = client.getValue() ? 1 : 0;
-                    Slog.d(TAG,
-                            "Updating attention value for user: " + userId + " to value: "
-                                    + settingsValue);
-                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue,
-                            userId);
-                }
-            }
-        });
-    }
-
-    private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId,
-            int feature, @Nullable ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient(
-                        mContext, this::getSession, token, listener, userId, opPackageName,
-                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                        feature);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleInternalCleanup(int userId,
-            @Nullable ClientMonitorCallback callback) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-            if (Flags.deHidl()) {
-                scheduleInternalCleanupAidl(userId, callback);
-            } else {
-                scheduleInternalCleanupHidl(userId, callback);
-            }
-        });
-    }
-
-    private void scheduleInternalCleanupHidl(int userId,
-            @Nullable ClientMonitorCallback callback) {
-        final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
-                mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
-                mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client,
-                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
-    }
-
-    private void scheduleInternalCleanupAidl(int userId,
-            @Nullable ClientMonitorCallback callback) {
-        final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient
-                client =
-                new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient(
-                        mContext, this::getSession, userId, mContext.getOpPackageName(),
-                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                        mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
-                        mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client,
-                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
-    }
-
-    @Override
-    public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable ClientMonitorCallback callback) {
-        scheduleInternalCleanup(userId, mBiometricStateCallback);
-    }
-
-    @Override
-    public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
-        scheduleInternalCleanup(userId, callback);
-    }
-
-    @Override
-    public void startPreparedClient(int sensorId, int cookie) {
-        mHandler.post(() -> {
-            mScheduler.startPreparedClient(cookie);
-        });
-    }
-
-    @Override
-    public void dumpProtoState(int sensorId, ProtoOutputStream proto,
-            boolean clearSchedulerBuffer) {
-        final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
-
-        proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
-        proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
-        proto.write(SensorStateProto.CURRENT_STRENGTH,
-                Utils.getCurrentStrength(mSensorProperties.sensorId));
-        proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
-
-        for (UserInfo user : UserManager.get(mContext).getUsers()) {
-            final int userId = user.getUserHandle().getIdentifier();
-
-            final long userToken = proto.start(SensorStateProto.USER_STATES);
-            proto.write(UserStateProto.USER_ID, userId);
-            proto.write(UserStateProto.NUM_ENROLLED, FaceUtils.getLegacyInstance(mSensorId)
-                    .getBiometricsForUser(mContext, userId).size());
-            proto.end(userToken);
-        }
-
-        proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
-                mSensorProperties.resetLockoutRequiresHardwareAuthToken);
-        proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
-                mSensorProperties.resetLockoutRequiresChallenge);
-
-        proto.end(sensorToken);
-    }
-
-    @Override
-    public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
-    }
-
-    @Override
-    public void dumpInternal(int sensorId, PrintWriter pw) {
-        PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(mSensorId);
-
-        JSONObject dump = new JSONObject();
-        try {
-            dump.put("service", TAG);
-
-            JSONArray sets = new JSONArray();
-            for (UserInfo user : UserManager.get(mContext).getUsers()) {
-                final int userId = user.getUserHandle().getIdentifier();
-                final int c = FaceUtils.getLegacyInstance(mSensorId)
-                        .getBiometricsForUser(mContext, userId).size();
-                JSONObject set = new JSONObject();
-                set.put("id", userId);
-                set.put("count", c);
-                set.put("accept", performanceTracker.getAcceptForUser(userId));
-                set.put("reject", performanceTracker.getRejectForUser(userId));
-                set.put("acquire", performanceTracker.getAcquireForUser(userId));
-                set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
-                set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
-                // cryptoStats measures statistics about secure face transactions
-                // (e.g. to unlock password storage, make secure purchases, etc.)
-                set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
-                set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
-                set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
-                sets.put(set);
-            }
-
-            dump.put("prints", sets);
-        } catch (JSONException e) {
-            Slog.e(TAG, "dump formatting failure", e);
-        }
-        pw.println(dump);
-        pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-
-        mScheduler.dump(pw);
-        mUsageStats.print(pw);
-    }
-
-    private void scheduleLoadAuthenticatorIds() {
-        // Note that this can be performed on the scheduler (as opposed to being done immediately
-        // when the HAL is (re)loaded, since
-        // 1) If this is truly the first time it's being performed (e.g. system has just started),
-        //    this will be run very early and way before any applications need to generate keys.
-        // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
-        //    just been reloaded), the framework already has a cache of the authenticatorIds. This
-        //    is safe because authenticatorIds only change when A) new template has been enrolled,
-        //    or B) all templates are removed.
-        mHandler.post(() -> {
-            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
-                final int targetUserId = user.id;
-                if (!mAuthenticatorIds.containsKey(targetUserId)) {
-                    scheduleUpdateActiveUserWithoutHandler(targetUserId);
-                }
-            }
-        });
-    }
-
-    /**
-     * Schedules the {@link FaceUpdateActiveUserClient} without posting the work onto the handler.
-     * Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
-     * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
-     * this operation on the same lambda/runnable as those operations so that the ordering is
-     * correct.
-     */
-    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
-        final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
-        final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
-                mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
-                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, hasEnrolled, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                if (success) {
-                    if (mCurrentUserId != targetUserId) {
-                        // Create new session with updated user ID
-                        mSession = null;
-                    }
-                    mCurrentUserId = targetUserId;
-                } else {
-                    Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
-                }
-            }
-        });
-    }
-
-    private BiometricLogger createLogger(int statsAction, int statsClient,
-            AuthenticationStatsCollector authenticationStatsCollector) {
-        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient, authenticationStatsCollector);
-    }
-
-    /**
-     * Sends a debug message to the HAL with the provided FileDescriptor and arguments.
-     */
-    public void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args) {
-        // WARNING: CDD restricts image data from leaving TEE unencrypted on
-        //          production devices:
-        // [C-1-10] MUST not allow unencrypted access to identifiable biometric
-        //          data or any data derived from it (such as embeddings) to the
-        //         Application Processor outside the context of the TEE.
-        //  As such, this API should only be enabled for testing purposes on
-        //  engineering and userdebug builds.  All modules in the software stack
-        //  MUST enforce final build products do NOT have this functionality.
-        //  Additionally, the following check MUST NOT be removed.
-        if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
-            return;
-        }
-
-        // Additionally, this flag allows turning off face for a device
-        // (either permanently through the build or on an individual device).
-        if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
-                || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
-            return;
-        }
-
-        // The debug method takes two file descriptors. The first is for text
-        // output, which we will drop.  The second is for binary data, which
-        // will be the protobuf data.
-        final IBiometricsFace daemon = getDaemon();
-        if (daemon != null) {
-            FileOutputStream devnull = null;
-            try {
-                devnull = new FileOutputStream("/dev/null");
-                final NativeHandle handle = new NativeHandle(
-                        new FileDescriptor[]{devnull.getFD(), fd},
-                        new int[0], false);
-                daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
-            } catch (IOException | RemoteException ex) {
-                Slog.d(TAG, "error while reading face debugging data", ex);
-            } finally {
-                if (devnull != null) {
-                    try {
-                        devnull.close();
-                    } catch (IOException ex) {
-                    }
-                }
-            }
-        }
-    }
-
-    void setTestHalEnabled(boolean enabled) {
-        mTestHalEnabled = enabled;
-    }
-
-    @NonNull
-    @Override
-    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
-            @NonNull String opPackageName) {
-        return new BiometricTestSessionImpl(mContext, mSensorId, callback,
-                this, mHalResultController);
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
deleted file mode 100644
index e44b263..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.FaceAuthenticateOptions;
-import android.hardware.face.FaceManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.face.UsageStats;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-class FaceAuthenticationClient
-        extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> {
-
-    private static final String TAG = "FaceAuthenticationClient";
-
-    private final UsageStats mUsageStats;
-
-    private final int[] mBiometricPromptIgnoreList;
-    private final int[] mBiometricPromptIgnoreListVendor;
-    private final int[] mKeyguardIgnoreList;
-    private final int[] mKeyguardIgnoreListVendor;
-
-    private int mLastAcquire;
-    private SensorPrivacyManager mSensorPrivacyManager;
-    @NonNull
-    private final AuthenticationStateListeners mAuthenticationStateListeners;
-
-    FaceAuthenticationClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFace> lazyDaemon,
-            @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, long operationId,
-            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
-            boolean requireConfirmation,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
-            @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength,
-            @NonNull AuthenticationStateListeners authenticationStateListeners) {
-        super(context, lazyDaemon, token, listener, operationId, restricted,
-                options, cookie, requireConfirmation, logger, biometricContext,
-                isStrongBiometric, null /* taskStackListener */,
-                lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
-                sensorStrength);
-        setRequestId(requestId);
-        mUsageStats = usageStats;
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
-        mAuthenticationStateListeners = authenticationStateListeners;
-
-        final Resources resources = getContext().getResources();
-        mBiometricPromptIgnoreList = resources.getIntArray(
-                R.array.config_face_acquire_biometricprompt_ignorelist);
-        mBiometricPromptIgnoreListVendor = resources.getIntArray(
-                R.array.config_face_acquire_vendor_biometricprompt_ignorelist);
-        mKeyguardIgnoreList = resources.getIntArray(
-                R.array.config_face_acquire_keyguard_ignorelist);
-        mKeyguardIgnoreListVendor = resources.getIntArray(
-                R.array.config_face_acquire_vendor_keyguard_ignorelist);
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-        mState = STATE_STARTED;
-    }
-
-    @NonNull
-    @Override
-    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(
-                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
-    }
-
-    @Override
-    protected void startHalOperation() {
-
-        if (mSensorPrivacyManager != null
-                && mSensorPrivacyManager
-                .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
-                SensorPrivacyManager.Sensors.CAMERA)) {
-            onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-            return;
-        }
-
-        try {
-            getFreshDaemon().authenticate(mOperationId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting auth", e);
-            onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    protected void stopHalOperation() {
-        try {
-            getFreshDaemon().cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting cancel", e);
-            onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public boolean wasUserDetected() {
-        // Do not provide haptic feedback if the user was not detected, and an error (usually
-        // ERROR_TIMEOUT) is received.
-        return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
-                && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
-    }
-
-    @Override
-    protected void handleLifecycleAfterAuth(boolean authenticated) {
-        // For face, the authentication lifecycle ends either when
-        // 1) Authenticated == true
-        // 2) Error occurred
-        // 3) Authenticated == false
-        mCallback.onClientFinished(this, true /* success */);
-    }
-
-    @Override
-    public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                getLockoutTracker().getLockoutModeForUser(userId);
-        final PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(getSensorId());
-        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
-            performanceTracker.incrementPermanentLockoutForUser(userId);
-        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
-            performanceTracker.incrementTimedLockoutForUser(userId);
-        }
-
-        return lockoutMode;
-    }
-
-    @Override
-    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
-            boolean authenticated, ArrayList<Byte> token) {
-        super.onAuthenticated(identifier, authenticated, token);
-
-        mState = STATE_STOPPED;
-        mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
-                getStartTimeMs(),
-                System.currentTimeMillis() - getStartTimeMs() /* latency */,
-                authenticated,
-                0 /* error */,
-                0 /* vendorError */,
-                getTargetUserId()));
-
-        if (reportBiometricAuthAttempts()) {
-            if (authenticated) {
-                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
-                        getTargetUserId());
-            } else {
-                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
-                        getTargetUserId());
-            }
-        }
-    }
-
-    @Override
-    public void onError(@BiometricConstants.Errors int error, int vendorCode) {
-        mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
-                getStartTimeMs(),
-                System.currentTimeMillis() - getStartTimeMs() /* latency */,
-                false /* authenticated */,
-                error,
-                vendorCode,
-                getTargetUserId()));
-
-        super.onError(error, vendorCode);
-    }
-
-    private int[] getAcquireIgnorelist() {
-        return isBiometricPrompt() ? mBiometricPromptIgnoreList : mKeyguardIgnoreList;
-    }
-
-    private int[] getAcquireVendorIgnorelist() {
-        return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
-    }
-
-    private boolean shouldSend(int acquireInfo, int vendorCode) {
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
-            return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
-        } else {
-            return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
-        }
-    }
-
-    @Override
-    public void onAcquired(int acquireInfo, int vendorCode) {
-        mLastAcquire = acquireInfo;
-
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) {
-            BiometricNotificationUtils.showReEnrollmentNotification(getContext());
-        }
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                getLockoutTracker().getLockoutModeForUser(getTargetUserId());
-        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
-            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
-            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
-        }
-
-        final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
-        onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
deleted file mode 100644
index 815cf91..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.hardware.face.Face;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.FaceManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-import android.view.Surface;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnrollClient;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Supplier;
-
-/**
- * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0} HIDL
- * interface.
- */
-public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
-
-    private static final String TAG = "FaceEnrollClient";
-
-    @NonNull private final int[] mDisabledFeatures;
-    @NonNull private final int[] mEnrollIgnoreList;
-    @NonNull private final int[] mEnrollIgnoreListVendor;
-
-    FaceEnrollClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
-            @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
-            @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
-            @Nullable Surface previewSurface, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull FaceEnrollOptions options) {
-        super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
-                BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
-        setRequestId(requestId);
-        mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
-        mEnrollIgnoreList = getContext().getResources()
-                .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
-        mEnrollIgnoreListVendor = getContext().getResources()
-                .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
-
-        Slog.w(TAG, "EnrollOptions "
-                + FaceEnrollOptions.enrollReasonToString(options.getEnrollReason()));
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-
-        BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
-        BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
-    }
-
-    @NonNull
-    @Override
-    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(
-                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
-    }
-
-    @Override
-    protected boolean hasReachedEnrollmentLimit() {
-        final int limit = getContext().getResources().getInteger(
-                com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
-        final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
-                .size();
-        if (enrolled >= limit) {
-            Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onAcquired(int acquireInfo, int vendorCode) {
-        final boolean shouldSend;
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
-            shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
-        } else {
-            shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
-        }
-        onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
-    }
-
-    @Override
-    protected void startHalOperation() {
-        final ArrayList<Byte> token = new ArrayList<>();
-        for (byte b : mHardwareAuthToken) {
-            token.add(b);
-        }
-        final ArrayList<Integer> disabledFeatures = new ArrayList<>();
-        for (int disabledFeature : mDisabledFeatures) {
-            disabledFeatures.add(disabledFeature);
-        }
-
-        try {
-            final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
-
-            if (status != Status.OK) {
-                onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
-                mCallback.onClientFinished(this, false /* success */);
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting enroll", e);
-            onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    protected void stopHalOperation() {
-        try {
-            getFreshDaemon().cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting cancel", e);
-            onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
deleted file mode 100644
index 97838a7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Face-specific generateChallenge client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
-
-    private static final String TAG = "FaceGenerateChallengeClient";
-    static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
-    private static final ClientMonitorCallback EMPTY_CALLBACK = new ClientMonitorCallback() {
-    };
-
-    private final long mCreatedAt;
-    private List<IFaceServiceReceiver> mWaiting;
-    private Long mChallengeResult;
-
-    FaceGenerateChallengeClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, long now) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
-                biometricContext);
-        mCreatedAt = now;
-        mWaiting = new ArrayList<>();
-    }
-
-    @Override
-    protected void startHalOperation() {
-        mChallengeResult = null;
-        try {
-            mChallengeResult = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
-            // send the result to the original caller via mCallback and any waiting callers
-            // that called reuseResult
-            sendChallengeResult(getListener(), mCallback);
-            for (IFaceServiceReceiver receiver : mWaiting) {
-                sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "generateChallenge failed", e);
-            mCallback.onClientFinished(this, false /* success */);
-        } finally {
-            mWaiting = null;
-        }
-    }
-
-    /** @return An arbitrary time value for caching provided to the constructor. */
-    public long getCreatedAt() {
-        return mCreatedAt;
-    }
-
-    /**
-     * Reuse the result of this operation when it is available. The receiver will be notified
-     * immediately if a challenge has already been generated.
-     *
-     * @param receiver receiver to be notified of challenge result
-     */
-    public void reuseResult(@NonNull IFaceServiceReceiver receiver) {
-        if (mWaiting != null) {
-            mWaiting.add(receiver);
-        } else {
-            sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
-        }
-    }
-
-    private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull ClientMonitorCallback ownerCallback) {
-        Preconditions.checkState(mChallengeResult != null, "result not available");
-        try {
-            receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult);
-            ownerCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-            ownerCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
deleted file mode 100644
index 47aaeec..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.OptionalBool;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.function.Supplier;
-
-/**
- * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
-
-    private static final String TAG = "FaceGetFeatureClient";
-
-    private final int mFeature;
-    private final int mFaceId;
-    private boolean mValue;
-
-    FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
-            @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            int feature, int faceId) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                logger, biometricContext);
-        mFeature = feature;
-        mFaceId = faceId;
-    }
-
-    @Override
-    public void unableToStart() {
-        try {
-            getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to send error", e);
-        }
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-        startHalOperation();
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId);
-            int[] features = new int[1];
-            boolean[] featureState = new boolean[1];
-            features[0] = mFeature;
-            featureState[0] = result.value;
-            mValue = result.value;
-
-            getListener().onFeatureGet(result.status == Status.OK, features, featureState);
-            mCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to getFeature", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    boolean getValue() {
-        return mValue;
-    }
-
-    @Override
-    public int getProtoEnum() {
-        return BiometricsProto.CM_GET_FEATURE;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
deleted file mode 100644
index 89a17c6..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalCleanupClient;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Face-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
-
-    FaceInternalCleanupClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
-            int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext,
-            @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                utils, authenticatorIds);
-    }
-
-    @Override
-    protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
-            Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
-            List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
-        return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId, logger, biometricContext);
-    }
-
-    @Override
-    protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
-            Supplier<IBiometricsFace> lazyDaemon, IBinder token,
-            int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            Map<Integer, Long> authenticatorIds) {
-        // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
-        // is all done internally.
-        return new FaceRemovalClient(context, lazyDaemon, token,
-                null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, logger, biometricContext, authenticatorIds);
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
deleted file mode 100644
index 250dd7e..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Face-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> {
-    private static final String TAG = "FaceInternalEnumerateClient";
-
-    FaceInternalEnumerateClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
-            @NonNull String owner, @NonNull List<Face> enrolledList,
-            @NonNull BiometricUtils<Face> utils, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
-        super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                logger, biometricContext);
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            getFreshDaemon().enumerate();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting enumerate", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
deleted file mode 100644
index 0ee7a35..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
-    private static final String TAG = "FaceRemovalClient";
-
-    private final int mBiometricId;
-
-    FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
-            @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
-            int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
-                biometricContext, authenticatorIds);
-        mBiometricId = biometricId;
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            getFreshDaemon().remove(mBiometricId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting remove", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
deleted file mode 100644
index f29b9e8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> {
-
-    private static final String TAG = "FaceResetLockoutClient";
-
-    private final ArrayList<Byte> mHardwareAuthToken;
-
-    FaceResetLockoutClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull byte[] hardwareAuthToken) {
-        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
-
-        mHardwareAuthToken = new ArrayList<>();
-        for (byte b : hardwareAuthToken) {
-            mHardwareAuthToken.add(b);
-        }
-    }
-
-    @Override
-    public void unableToStart() {
-        // Nothing to do here
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-        startHalOperation();
-    }
-
-    public boolean interruptsPrecedingClients() {
-        return true;
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            getFreshDaemon().resetLockout(mHardwareAuthToken);
-            mCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to reset lockout", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public int getProtoEnum() {
-        return BiometricsProto.CM_RESET_LOCKOUT;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
deleted file mode 100644
index b7b0dc04..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> {
-
-    private static final String TAG = "FaceRevokeChallengeClient";
-
-    FaceRevokeChallengeClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
-        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            getFreshDaemon().revokeChallenge();
-            mCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "revokeChallenge failed", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
deleted file mode 100644
index 3c82f9c..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> {
-
-    private static final String TAG = "FaceSetFeatureClient";
-
-    private final int mFeature;
-    private final boolean mEnabled;
-    private final ArrayList<Byte> mHardwareAuthToken;
-    private final int mFaceId;
-
-    FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
-            @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                logger, biometricContext);
-        mFeature = feature;
-        mEnabled = enabled;
-        mFaceId = faceId;
-
-        mHardwareAuthToken = new ArrayList<>();
-        for (byte b : hardwareAuthToken) {
-            mHardwareAuthToken.add(b);
-        }
-    }
-
-    @Override
-    public void unableToStart() {
-        try {
-            getListener().onFeatureSet(false /* success */, mFeature);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to send error", e);
-        }
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-
-        startHalOperation();
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            final int result = getFreshDaemon()
-                    .setFeature(mFeature, mEnabled, mHardwareAuthToken, mFaceId);
-            getListener().onFeatureSet(result == Status.OK, mFeature);
-            mCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public int getProtoEnum() {
-        return BiometricsProto.CM_SET_FEATURE;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index a004cae4..9a4c29d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -173,9 +173,14 @@
     }
 
     private AidlResponseHandler getAidlResponseHandler() {
-        return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
-                mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
-                mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+        return new AidlResponseHandler(getContext(),
+                getScheduler(),
+                getSensorProperties().sensorId,
+                mCurrentUserId,
+                mLockoutTracker,
+                mLockoutResetDispatcher,
+                mAuthSessionCoordinator,
+                mAidlResponseHandlerCallback);
     }
 
     private IBiometricsFace getIBiometricsFace() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index fa95361..45d0cfe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -53,7 +53,7 @@
 
     private static final String TAG = "HidlToAidlSessionAdapter";
 
-    private static final int CHALLENGE_TIMEOUT_SEC = 600;
+    @VisibleForTesting static final int CHALLENGE_TIMEOUT_SEC = 600;
     @DurationMillisLong
     private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
     @DurationMillisLong
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index d762914..deda93c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -72,7 +72,6 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Pair;
 import android.util.Slog;
@@ -83,7 +82,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -94,12 +92,8 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
-import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
-import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
-import com.google.android.collect.Lists;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -886,9 +880,9 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
-        public void registerAuthenticatorsLegacy(
+        public void registerAuthenticators(
                 @NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
-            super.registerAuthenticatorsLegacy_enforcePermission();
+            super.registerAuthenticators_enforcePermission();
             if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
                 Slog.d(TAG, "No fingerprint sensors available.");
                 return;
@@ -897,30 +891,6 @@
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
-        @Override // Binder call
-        public void registerAuthenticators(
-                @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-            super.registerAuthenticators_enforcePermission();
-
-            mRegistry.registerAll(() -> {
-                List<String> aidlSensors = new ArrayList<>();
-                final String[] instances = mAidlInstanceNameSupplier.get();
-                if (instances != null) {
-                    aidlSensors.addAll(Lists.newArrayList(instances));
-                }
-
-                final Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
-                        filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
-                final List<ServiceProvider> providers = new ArrayList<>();
-                providers.addAll(getHidlProviders(filteredInstances.first));
-                providers.addAll(getAidlProviders(filteredInstances.second));
-
-                return providers;
-            });
-        }
-
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFingerprintAuthenticatorsRegisteredCallback callback) {
@@ -1086,22 +1056,15 @@
 
                     return null;
                 };
-        if (Flags.deHidl()) {
-            mFingerprintProviderFunction = fingerprintProviderFunction == null
-                    ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
-                            new FingerprintProvider(
-                                    getContext(), mBiometricStateCallback,
-                                    mAuthenticationStateListeners,
-                                    filteredSensorProps.second,
-                                    filteredSensorProps.first, mLockoutResetDispatcher,
-                                    mGestureAvailabilityDispatcher,
-                                    mBiometricContext,
-                                    resetLockoutRequiresHardwareAuthToken)
-                    : fingerprintProviderFunction;
-        } else {
-            mFingerprintProviderFunction =
-                    (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
-        }
+        mFingerprintProviderFunction = fingerprintProviderFunction != null
+                ? fingerprintProviderFunction :
+                        (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+                                new FingerprintProvider(getContext(), mBiometricStateCallback,
+                                        mAuthenticationStateListeners, filteredSensorProps.second,
+                                        filteredSensorProps.first, mLockoutResetDispatcher,
+                                        mGestureAvailabilityDispatcher, mBiometricContext,
+                                        resetLockoutRequiresHardwareAuthToken);
+
         mHandler = new Handler(Looper.getMainLooper());
         mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1160,74 +1123,6 @@
                 .getSensorPropForInstance(finalSensorInstance));
     }
 
-    private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
-                filterAvailableHalInstances(
-            @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
-            @NonNull List<String> aidlInstances) {
-        if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
-            return new Pair(hidlInstances, aidlInstances);
-        }
-
-        final int virtualAt = aidlInstances.indexOf("virtual");
-        if (Utils.isFingerprintVirtualEnabled(getContext())) {
-            if (virtualAt != -1) {
-                //only virtual instance should be returned
-                Slog.i(TAG, "virtual hal is used");
-                return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
-            } else {
-                Slog.e(TAG, "Could not find virtual interface while it is enabled");
-                return new Pair(hidlInstances, aidlInstances);
-            }
-        } else {
-            //remove virtual instance
-            aidlInstances = new ArrayList<>(aidlInstances);
-            if (virtualAt != -1) {
-                aidlInstances.remove(virtualAt);
-            }
-            return new Pair(hidlInstances, aidlInstances);
-        }
-    }
-
-    @NonNull
-    private List<ServiceProvider> getHidlProviders(
-            @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-        final List<ServiceProvider> providers = new ArrayList<>();
-
-        for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
-            final Fingerprint21 fingerprint21;
-            if ((Build.IS_USERDEBUG || Build.IS_ENG)
-                    && getContext().getResources().getBoolean(R.bool.allow_test_udfps)
-                    && Settings.Secure.getIntForUser(getContext().getContentResolver(),
-                    Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
-                    UserHandle.USER_CURRENT) != 0) {
-                fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
-                        mBiometricStateCallback, mAuthenticationStateListeners,
-                        hidlSensor, mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
-                        BiometricContext.getInstance(getContext()));
-            } else {
-                fingerprint21 = Fingerprint21.newInstance(getContext(),
-                        mBiometricStateCallback, mAuthenticationStateListeners, hidlSensor,
-                        mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
-            }
-            providers.add(fingerprint21);
-        }
-
-        return providers;
-    }
-
-    @NonNull
-    private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
-        final List<ServiceProvider> providers = new ArrayList<>();
-
-        for (String instance : instances) {
-            final FingerprintProvider provider = mFingerprintProvider.apply(instance);
-            Slog.i(TAG, "Adding AIDL provider: " + instance);
-            providers.add(provider);
-        }
-
-        return providers;
-    }
-
     @Override
     public void onStart() {
         publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index bd21cf4..6d1715f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,7 +25,6 @@
 import android.hardware.keymaster.HardwareAuthToken;
 import android.util.Slog;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -53,16 +52,6 @@
     /**
      * Interface to send results to the AidlResponseHandler's owner.
      */
-    public interface HardwareUnavailableCallback {
-        /**
-         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-         */
-        void onHardwareUnavailable();
-    }
-
-    /**
-     * Interface to send results to the AidlResponseHandler's owner.
-     */
     public interface AidlResponseHandlerCallback {
         /**
          * Invoked when enrollment is successful.
@@ -90,8 +79,6 @@
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
-    private final HardwareUnavailableCallback mHardwareUnavailableCallback;
-    @NonNull
     private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     public AidlResponseHandler(@NonNull Context context,
@@ -99,24 +86,6 @@
             @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
-            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
-        this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
-                authSessionCoordinator, hardwareUnavailableCallback,
-                new AidlResponseHandlerCallback() {
-                    @Override
-                    public void onEnrollSuccess() {}
-
-                    @Override
-                    public void onHardwareUnavailable() {}
-                });
-    }
-
-    public AidlResponseHandler(@NonNull Context context,
-            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
-            @NonNull LockoutTracker lockoutTracker,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull AuthSessionCoordinator authSessionCoordinator,
-            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
             @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
         mContext = context;
         mScheduler = scheduler;
@@ -125,7 +94,6 @@
         mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
-        mHardwareUnavailableCallback = hardwareUnavailableCallback;
         mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
     }
 
@@ -171,11 +139,7 @@
         handleResponse(ErrorConsumer.class, (c) -> {
             c.onError(error, vendorCode);
             if (error == Error.HW_UNAVAILABLE) {
-                if (Flags.deHidl()) {
-                    mAidlResponseHandlerCallback.onHardwareUnavailable();
-                } else {
-                    mHardwareUnavailableCallback.onHardwareUnavailable();
-                }
+                mAidlResponseHandlerCallback.onHardwareUnavailable();
             }
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 93d1b6e..5edcbed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -31,23 +31,16 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Build;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -67,7 +60,6 @@
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 
-import java.time.Clock;
 import java.util.ArrayList;
 import java.util.function.Supplier;
 
@@ -79,30 +71,17 @@
         extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
-    private static final int MESSAGE_AUTH_SUCCESS = 2;
-    private static final int MESSAGE_FINGER_UP = 3;
     @NonNull
     private final SensorOverlays mSensorOverlays;
     @NonNull
     private final FingerprintSensorPropertiesInternal mSensorProps;
     @NonNull
     private final CallbackWithProbe<Probe> mALSProbeCallback;
-    private final Handler mHandler;
-    private final int mSkipWaitForPowerAcquireMessage;
-    private final int mSkipWaitForPowerVendorAcquireMessage;
-    private final long mFingerUpIgnoresPower = 500;
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     @Nullable
     private ICancellationSignal mCancellationSignal;
     private boolean mIsPointerDown;
-    private long mWaitForAuthKeyguard;
-    private long mWaitForAuthBp;
-    private long mIgnoreAuthFor;
-    private long mSideFpsLastAcquireStartTime;
-    private Runnable mAuthSuccessRunnable;
-    private final Clock mClock;
-
 
     public FingerprintAuthenticationClient(
             @NonNull Context context,
@@ -125,9 +104,7 @@
             @NonNull AuthenticationStateListeners authenticationStateListeners,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull Handler handler,
             @Authenticators.Types int biometricStrength,
-            @NonNull Clock clock,
             @Nullable LockoutTracker lockoutTracker) {
         super(
                 context,
@@ -156,39 +133,7 @@
         mAuthenticationStateListeners = authenticationStateListeners;
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
-        mHandler = handler;
-
-        mWaitForAuthKeyguard =
-                context.getResources()
-                        .getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow);
-        mWaitForAuthBp =
-                context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow);
-        mIgnoreAuthFor =
-                context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime);
-
-        mSkipWaitForPowerAcquireMessage =
-                context.getResources().getInteger(
-                        R.integer.config_sidefpsSkipWaitForPowerAcquireMessage);
-        mSkipWaitForPowerVendorAcquireMessage =
-                context.getResources().getInteger(
-                        R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
-        mSideFpsLastAcquireStartTime = -1;
-        mClock = clock;
-
-        if (mSensorProps.isAnySidefpsType()) {
-            if (Build.isDebuggable()) {
-                mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(),
-                        Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
-                        (int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT);
-                mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(),
-                        Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp,
-                        UserHandle.USER_CURRENT);
-                mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(),
-                        Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor,
-                        UserHandle.USER_CURRENT);
-            }
-        }
     }
 
     @Override
@@ -316,11 +261,7 @@
         }
 
         try {
-            if (Flags.deHidl()) {
-                startAuthentication();
-            } else {
-                mCancellationSignal = doAuthenticate();
-            }
+            doAuthenticate();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             onError(
@@ -334,49 +275,7 @@
         }
     }
 
-    private ICancellationSignal doAuthenticate() throws RemoteException {
-        final AidlSession session = getFreshDaemon();
-
-        final OperationContextExt opContext = getOperationContext();
-        final ICancellationSignal cancel;
-        if (session.hasContextMethods()) {
-            cancel = session.getSession().authenticateWithContext(
-                    mOperationId, opContext.toAidlContext(getOptions()));
-        } else {
-            cancel = session.getSession().authenticate(mOperationId);
-        }
-
-        getBiometricContext().subscribe(opContext, ctx -> {
-            if (session.hasContextMethods()) {
-                try {
-                    session.getSession().onContextChanged(ctx);
-                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
-                    if (ctx.operationState != null && ctx.operationState.getTag()
-                            == OperationState.fingerprintOperationState) {
-                        session.getSession().setIgnoreDisplayTouches(
-                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify context changed", e);
-                }
-            }
-
-            // TODO(b/243836005): this should come via ctx
-            final boolean isAwake = getBiometricContext().isAwake();
-            if (isAwake) {
-                mALSProbeCallback.getProbe().enable();
-            } else {
-                mALSProbeCallback.getProbe().disable();
-            }
-        });
-        if (getBiometricContext().isAwake()) {
-            mALSProbeCallback.getProbe().enable();
-        }
-
-        return cancel;
-    }
-
-    private void startAuthentication() {
+    private void doAuthenticate() throws RemoteException {
         final AidlSession session = getFreshDaemon();
         final OperationContextExt opContext = getOperationContext();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 8d2b46f..1db2fad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -31,7 +30,6 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -106,13 +104,8 @@
         resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
                 this);
-
         try {
-            if (Flags.deHidl()) {
-                startDetectInteraction();
-            } else {
-                mCancellationSignal = doDetectInteraction();
-            }
+            doDetectInteraction();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting finger detect", e);
             mSensorOverlays.hide(getSensorId());
@@ -120,33 +113,7 @@
         }
     }
 
-    private ICancellationSignal doDetectInteraction() throws RemoteException {
-        final AidlSession session = getFreshDaemon();
-
-        if (session.hasContextMethods()) {
-            final OperationContextExt opContext = getOperationContext();
-            final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
-                    opContext.toAidlContext(mOptions));
-            getBiometricContext().subscribe(opContext, ctx -> {
-                try {
-                    session.getSession().onContextChanged(ctx);
-                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
-                    if (ctx.operationState != null && ctx.operationState.getTag()
-                            == OperationState.fingerprintOperationState) {
-                        session.getSession().setIgnoreDisplayTouches(
-                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify context changed", e);
-                }
-            });
-            return cancel;
-        } else {
-            return session.getSession().detectInteraction();
-        }
-    }
-
-    private void startDetectInteraction() throws RemoteException {
+    private void doDetectInteraction() throws RemoteException {
         final AidlSession session = getFreshDaemon();
 
         if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index a24ab1d..86ebabe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -26,7 +26,6 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintEnrollOptions;
@@ -40,7 +39,6 @@
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -209,11 +207,7 @@
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
-            if (Flags.deHidl()) {
-                startEnroll();
-            } else {
-                mCancellationSignal = doEnroll();
-            }
+            doEnroll();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
@@ -222,35 +216,7 @@
         }
     }
 
-    private ICancellationSignal doEnroll() throws RemoteException {
-        final AidlSession session = getFreshDaemon();
-        final HardwareAuthToken hat =
-                HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
-
-        if (session.hasContextMethods()) {
-            final OperationContextExt opContext = getOperationContext();
-            final ICancellationSignal cancel = session.getSession().enrollWithContext(
-                    hat, opContext.toAidlContext());
-            getBiometricContext().subscribe(opContext, ctx -> {
-                try {
-                    session.getSession().onContextChanged(ctx);
-                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
-                    if (ctx.operationState != null && ctx.operationState.getTag()
-                            == OperationState.fingerprintOperationState) {
-                        session.getSession().setIgnoreDisplayTouches(
-                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify context changed", e);
-                }
-            });
-            return cancel;
-        } else {
-            return session.getSession().enroll(hat);
-        }
-    }
-
-    private void startEnroll() throws RemoteException {
+    private void doEnroll() throws RemoteException {
         final AidlSession session = getFreshDaemon();
         final HardwareAuthToken hat =
                 HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 9290f8a..beb3f2f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -29,12 +29,10 @@
 import android.content.res.TypedArray;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.common.ComponentInfo;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
@@ -50,10 +48,8 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -63,7 +59,6 @@
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -96,10 +91,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
 
 /**
  * Provider for a single instance of the {@link IFingerprint} HAL.
@@ -200,11 +193,7 @@
         mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFingerprintSensors = new SensorList<>(ActivityManager.getService());
-        if (Flags.deHidl()) {
-            mHandler = biometricHandlerProvider.getFingerprintHandler();
-        } else {
-            mHandler = new Handler(Looper.getMainLooper());
-        }
+        mHandler = biometricHandlerProvider.getFingerprintHandler();
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
@@ -230,66 +219,19 @@
 
     private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
             GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        if (Flags.deHidl()) {
-            if (!resetLockoutRequiresHardwareAuthToken) {
-                Slog.d(getTag(), "Adding HIDL configs");
-                for (SensorProps sensorConfig: props) {
-                    addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
-                            resetLockoutRequiresHardwareAuthToken);
-                }
-            } else {
-                Slog.d(getTag(), "Adding AIDL configs");
-                final List<SensorLocationInternal> workaroundLocations =
-                        getWorkaroundSensorProps(mContext);
-                for (SensorProps prop : props) {
-                    addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
-                            resetLockoutRequiresHardwareAuthToken);
-                }
+        if (!resetLockoutRequiresHardwareAuthToken) {
+            Slog.d(getTag(), "Adding HIDL configs");
+            for (SensorProps sensorConfig: props) {
+                addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+                        resetLockoutRequiresHardwareAuthToken);
             }
         } else {
+            Slog.d(getTag(), "Adding AIDL configs");
             final List<SensorLocationInternal> workaroundLocations =
                     getWorkaroundSensorProps(mContext);
-
             for (SensorProps prop : props) {
-                final int sensorId = prop.commonProps.sensorId;
-                final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-                if (prop.commonProps.componentInfo != null) {
-                    for (ComponentInfo info : prop.commonProps.componentInfo) {
-                        componentInfo.add(new ComponentInfoInternal(info.componentId,
-                                info.hardwareVersion, info.firmwareVersion, info.serialNumber,
-                                info.softwareVersion));
-                    }
-                }
-                final FingerprintSensorPropertiesInternal internalProp =
-                        new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
-                                prop.commonProps.sensorStrength,
-                                prop.commonProps.maxEnrollmentsPerUser,
-                                componentInfo,
-                                prop.sensorType,
-                                prop.halControlsIllumination,
-                                true /* resetLockoutRequiresHardwareAuthToken */,
-                                !workaroundLocations.isEmpty() ? workaroundLocations :
-                                        Arrays.stream(prop.sensorLocations).map(
-                                                        location -> new SensorLocationInternal(
-                                                                location.display,
-                                                                location.sensorLocationX,
-                                                                location.sensorLocationY,
-                                                                location.sensorRadius))
-                                                .collect(Collectors.toList()));
-                final Sensor sensor = new Sensor(this, mContext, mHandler, internalProp,
-                        mBiometricContext);
-                sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
-                final int sessionUserId =
-                        sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
-                                sensor.getLazySession().get().getUserId();
-                mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
-                        new SynchronousUserSwitchObserver() {
-                            @Override
-                            public void onUserSwitching(int newUserId) {
-                                scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
-                            }
-                        });
-                Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+                addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+                        resetLockoutRequiresHardwareAuthToken);
             }
         }
     }
@@ -546,23 +488,7 @@
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController,
                     mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason, options);
-            if (Flags.deHidl()) {
-                scheduleForSensor(sensorId, client, mBiometricStateCallback);
-            } else {
-                scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
-                        mBiometricStateCallback, new ClientMonitorCallback() {
-                            @Override
-                            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                                    boolean success) {
-                                ClientMonitorCallback.super.onClientFinished(
-                                        clientMonitor, success);
-                                if (success) {
-                                    scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                                    scheduleInvalidationRequest(sensorId, userId);
-                                }
-                            }
-                        }));
-            }
+            scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
         return id;
     }
@@ -605,13 +531,8 @@
             final int userId = options.getUserId();
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
-            final LockoutTracker lockoutTracker;
-            if (Flags.deHidl()) {
-                lockoutTracker = mFingerprintSensors.get(sensorId)
-                        .getLockoutTracker(true /* forAuth */);
-            } else {
-                lockoutTracker = null;
-            }
+            final LockoutTracker lockoutTracker = mFingerprintSensors.get(sensorId)
+                    .getLockoutTracker(true /* forAuth */);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
@@ -622,22 +543,16 @@
                     mTaskStackListener,
                     mUdfpsOverlayController, mSidefpsController,
                     mAuthenticationStateListeners, allowBackgroundAuthentication,
-                    mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
+                    mFingerprintSensors.get(sensorId).getSensorProperties(),
                     Utils.getCurrentStrength(sensorId),
-                    SystemClock.elapsedRealtimeClock(),
                     lockoutTracker);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
 
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                     mBiometricStateCallback.onClientStarted(clientMonitor);
-                    if (Flags.deHidl()) {
-                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
-                                mAuthSessionCoordinator.authStartedFor(userId, sensorId,
-                                        requestId));
-                    } else {
-                        mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
-                    }
+                    mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                            mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId));
                 }
 
                 @Override
@@ -649,15 +564,10 @@
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
                     mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                    if (Flags.deHidl()) {
-                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
-                                mAuthSessionCoordinator.authEndedFor(userId,
-                                        Utils.getCurrentStrength(sensorId), sensorId, requestId,
-                                        success));
-                    } else {
-                        mAuthSessionCoordinator.authEndedFor(userId,
-                                Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
-                    }
+                    mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                            mAuthSessionCoordinator.authEndedFor(userId,
+                                    Utils.getCurrentStrength(sensorId), sensorId, requestId,
+                                    success));
                 }
             });
 
@@ -764,10 +674,7 @@
 
     @Override
     public boolean isHardwareDetected(int sensorId) {
-        if (Flags.deHidl()) {
-            return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
-        }
-        return hasHalInstance();
+        return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
     }
 
     @Override
@@ -805,12 +712,7 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        if (Flags.deHidl()) {
-            return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
-        } else {
-            return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
-                    Utils.getCurrentStrength(sensorId));
-        }
+        return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index af88c62..b7e3f70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -42,7 +42,6 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -58,7 +57,6 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -110,13 +108,6 @@
     }
 
     Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull BiometricContext biometricContext) {
-        this(provider, context, handler, sensorProperties,
-                biometricContext, null);
-    }
-
-    Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull SensorProps sensorProp,
             @NonNull BiometricContext biometricContext,
             @NonNull List<SensorLocationInternal> workaroundLocation,
@@ -131,13 +122,8 @@
      */
     public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-        if (Flags.deHidl()) {
-            setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
-                    lockoutResetDispatcher));
-        } else {
-            setScheduler(getUserAwareBiometricSchedulerForInit(gestureAvailabilityDispatcher,
-                    lockoutResetDispatcher));
-        }
+        setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+                lockoutResetDispatcher));
         mLockoutTracker = new LockoutCache();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
     }
@@ -168,7 +154,7 @@
                         final AidlResponseHandler resultController = new AidlResponseHandler(
                                 mContext, mScheduler, sensorId, newUserId,
                                 mLockoutTracker, lockoutResetDispatcher,
-                                mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                mBiometricContext.getAuthSessionCoordinator(),
                                 new AidlResponseHandler.AidlResponseHandlerCallback() {
                                     @Override
                                     public void onEnrollSuccess() {
@@ -192,45 +178,6 @@
                 });
     }
 
-    private UserAwareBiometricScheduler<ISession, AidlSession>
-            getUserAwareBiometricSchedulerForInit(
-                    GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-                    LockoutResetDispatcher lockoutResetDispatcher) {
-        return new UserAwareBiometricScheduler<>(TAG,
-                BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
-                gestureAvailabilityDispatcher,
-                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
-                new UserAwareBiometricScheduler.UserSwitchCallback() {
-                    @NonNull
-                    @Override
-                    public StopUserClient<ISession> getStopUserClient(int userId) {
-                        return new FingerprintStopUserClient(mContext,
-                                () -> mLazySession.get().getSession(), mToken,
-                                userId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                () -> mCurrentSession = null);
-                    }
-
-                    @NonNull
-                    @Override
-                    public StartUserClient<IFingerprint, ISession> getStartUserClient(
-                            int newUserId) {
-                        final int sensorId = mSensorProperties.sensorId;
-
-                        final AidlResponseHandler resultController = new AidlResponseHandler(
-                                mContext, mScheduler, sensorId, newUserId,
-                                mLockoutTracker, lockoutResetDispatcher,
-                                mBiometricContext.getAuthSessionCoordinator(), () -> {
-                                    Slog.e(TAG, "Fingerprint hardware unavailable.");
-                                    mCurrentSession = null;
-                                });
-
-                        return Sensor.this.getStartUserClient(resultController, sensorId,
-                                newUserId);
-                    }
-                });
-    }
-
     private FingerprintStartUserClient getStartUserClient(AidlResponseHandler resultController,
             int sensorId, int newUserId) {
         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
deleted file mode 100644
index fc037ae..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * A test session implementation for the {@link Fingerprint21} provider. See
- * {@link android.hardware.biometrics.BiometricTestSession}.
- */
-public class BiometricTestSessionImpl extends ITestSession.Stub {
-
-    private static final String TAG = "BiometricTestSessionImpl";
-
-    @NonNull private final Context mContext;
-    private final int mSensorId;
-    @NonNull private final ITestSessionCallback mCallback;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final Fingerprint21 mFingerprint21;
-    @NonNull private final Fingerprint21.HalResultController mHalResultController;
-    @NonNull private final Set<Integer> mEnrollmentIds;
-    @NonNull private final Random mRandom;
-
-    /**
-     * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
-     * test, since enrollment is a platform-only API. The authentication path is tested through
-     * the public FingerprintManager APIs and does not use this receiver.
-     */
-    private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
-        @Override
-        public void onEnrollResult(Fingerprint fp, int remaining) {
-
-        }
-
-        @Override
-        public void onAcquired(int acquiredInfo, int vendorCode) {
-
-        }
-
-        @Override
-        public void onAuthenticationSucceeded(Fingerprint fp, int userId,
-                boolean isStrongBiometric) {
-
-        }
-
-        @Override
-        public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
-
-        }
-
-        @Override
-        public void onAuthenticationFailed() {
-
-        }
-
-        @Override
-        public void onError(int error, int vendorCode) {
-
-        }
-
-        @Override
-        public void onRemoved(Fingerprint fp, int remaining) {
-
-        }
-
-        @Override
-        public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-
-        }
-
-        @Override
-        public void onUdfpsPointerDown(int sensorId) {
-
-        }
-
-        @Override
-        public void onUdfpsPointerUp(int sensorId) {
-
-        }
-
-        @Override
-        public void onUdfpsOverlayShown() {
-
-        }
-    };
-
-    BiometricTestSessionImpl(@NonNull Context context, int sensorId,
-            @NonNull ITestSessionCallback callback,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull Fingerprint21 fingerprint21,
-            @NonNull Fingerprint21.HalResultController halResultController) {
-        mContext = context;
-        mSensorId = sensorId;
-        mCallback = callback;
-        mFingerprint21 = fingerprint21;
-        mBiometricStateCallback = biometricStateCallback;
-        mHalResultController = halResultController;
-        mEnrollmentIds = new HashSet<>();
-        mRandom = new Random();
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void setTestHalEnabled(boolean enabled) {
-
-        super.setTestHalEnabled_enforcePermission();
-
-        mFingerprint21.setTestHalEnabled(enabled);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void startEnroll(int userId) {
-
-        super.startEnroll_enforcePermission();
-
-        mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
-                (new FingerprintEnrollOptions.Builder()).build());
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void finishEnroll(int userId) {
-
-        super.finishEnroll_enforcePermission();
-
-        int nextRandomId = mRandom.nextInt();
-        while (mEnrollmentIds.contains(nextRandomId)) {
-            nextRandomId = mRandom.nextInt();
-        }
-
-        mEnrollmentIds.add(nextRandomId);
-        mHalResultController.onEnrollResult(0 /* deviceId */,
-                nextRandomId /* fingerId */, userId, 0);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void acceptAuthentication(int userId)  {
-
-        // Fake authentication with any of the existing fingers
-        super.acceptAuthentication_enforcePermission();
-
-        List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId)
-                .getBiometricsForUser(mContext, userId);
-        if (fingerprints.isEmpty()) {
-            Slog.w(TAG, "No fingerprints, returning");
-            return;
-        }
-        final int fid = fingerprints.get(0).getBiometricId();
-        final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
-        mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void rejectAuthentication(int userId)  {
-
-        super.rejectAuthentication_enforcePermission();
-
-        mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void notifyAcquired(int userId, int acquireInfo)  {
-
-        super.notifyAcquired_enforcePermission();
-
-        mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void notifyError(int userId, int errorCode)  {
-
-        super.notifyError_enforcePermission();
-
-        mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    @Override
-    public void cleanupInternalState(int userId)  {
-
-        super.cleanupInternalState_enforcePermission();
-
-        mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                try {
-                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception", e);
-                }
-            }
-
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                try {
-                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception", e);
-                }
-            }
-        });
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
deleted file mode 100644
index 33e448b..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ /dev/null
@@ -1,1289 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.app.UserSwitchObserver;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
-import com.android.server.biometrics.AuthenticationStatsCollector;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.SensorServiceStateProto;
-import com.android.server.biometrics.SensorStateProto;
-import com.android.server.biometrics.UserStateProto;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
-import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
-import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
-import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Supplier;
-
-/**
- * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
- * its extended minor versions.
- */
-public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider {
-
-    private static final String TAG = "Fingerprint21";
-    private static final int ENROLL_TIMEOUT_SEC = 60;
-
-    private boolean mTestHalEnabled;
-
-    final Context mContext;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
-    private final ActivityTaskManager mActivityTaskManager;
-    @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    private final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
-    private final Handler mHandler;
-    private final LockoutResetDispatcher mLockoutResetDispatcher;
-    private final LockoutFrameworkImpl mLockoutTracker;
-    private final BiometricTaskStackListener mTaskStackListener;
-    private final Supplier<IBiometricsFingerprint> mLazyDaemon;
-    private final Map<Integer, Long> mAuthenticatorIds;
-
-    @Nullable private IBiometricsFingerprint mDaemon;
-    @NonNull private final HalResultController mHalResultController;
-    @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Nullable private ISidefpsController mSidefpsController;
-    @NonNull private final BiometricContext mBiometricContext;
-    @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
-    // for requests that do not use biometric prompt
-    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-    private int mCurrentUserId = UserHandle.USER_NULL;
-    private final boolean mIsUdfps;
-    private final int mSensorId;
-    private final boolean mIsPowerbuttonFps;
-    private AidlSession mSession;
-
-    private final class BiometricTaskStackListener extends TaskStackListener {
-        @Override
-        public void onTaskStackChanged() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationClient)) {
-                    Slog.e(TAG, "Task stack changed for client: " + client);
-                    return;
-                }
-                if (Utils.isKeyguard(mContext, client.getOwnerString())
-                        || Utils.isSystem(mContext, client.getOwnerString())) {
-                    return; // Keyguard is always allowed
-                }
-
-                if (Utils.isBackground(client.getOwnerString())
-                        && !client.isAlreadyDone()) {
-                    Slog.e(TAG, "Stopping background authentication,"
-                            + " currentClient: " + client);
-                    mScheduler.cancelAuthenticationOrDetection(
-                            client.getToken(), client.getRequestId());
-                }
-            });
-        }
-    }
-
-    private final LockoutFrameworkImpl.LockoutResetCallback mLockoutResetCallback =
-            new LockoutFrameworkImpl.LockoutResetCallback() {
-                @Override
-                public void onLockoutReset(int userId) {
-                    mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
-                }
-            };
-
-    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
-        @Override
-        public void onUserSwitching(int newUserId) {
-            scheduleInternalCleanup(newUserId, null /* callback */);
-        }
-    };
-
-    public static class HalResultController extends IBiometricsFingerprintClientCallback.Stub {
-
-        /**
-         * Interface to sends results to the HalResultController's owner.
-         */
-        public interface Callback {
-            /**
-             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-             */
-            void onHardwareUnavailable();
-        }
-
-        private final int mSensorId;
-        @NonNull private final Context mContext;
-        @NonNull final Handler mHandler;
-        @NonNull final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
-        @Nullable private Callback mCallback;
-
-        HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler) {
-            mSensorId = sensorId;
-            mContext = context;
-            mHandler = handler;
-            mScheduler = scheduler;
-        }
-
-        public void setCallback(@Nullable Callback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintEnrollClient)) {
-                    Slog.e(TAG, "onEnrollResult for non-enroll client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final int currentUserId = client.getTargetUserId();
-                final CharSequence name = FingerprintUtils.getLegacyInstance(mSensorId)
-                        .getUniqueName(mContext, currentUserId);
-                final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
-
-                final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
-                enrollClient.onEnrollResult(fingerprint, remaining);
-            });
-        }
-
-        @Override
-        public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
-            onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
-        }
-
-        @Override
-        public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AcquisitionClient)) {
-                    Slog.e(TAG, "onAcquired for non-acquisition client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
-                acquisitionClient.onAcquired(acquiredInfo, vendorCode);
-            });
-        }
-
-        @Override
-        public void onAuthenticated(long deviceId, int fingerId, int groupId,
-                ArrayList<Byte> token) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final boolean authenticated = fingerId != 0;
-                final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
-                authenticationConsumer.onAuthenticated(fp, authenticated, token);
-            });
-        }
-
-        @Override
-        public void onError(long deviceId, int error, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                Slog.d(TAG, "handleError"
-                        + ", client: " + Utils.getClientName(client)
-                        + ", error: " + error
-                        + ", vendorCode: " + vendorCode);
-                if (!(client instanceof ErrorConsumer)) {
-                    Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
-                    return;
-                }
-
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(error, vendorCode);
-
-                if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
-                    Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
-                    if (mCallback != null) {
-                        mCallback.onHardwareUnavailable();
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
-            mHandler.post(() -> {
-                Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining);
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof RemovalConsumer)) {
-                    Slog.e(TAG, "onRemoved for non-removal consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
-                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-                removalConsumer.onRemoved(fp, remaining);
-            });
-        }
-
-        @Override
-        public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof EnumerateConsumer)) {
-                    Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
-                final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
-                enumerateConsumer.onEnumerationResult(fp, remaining);
-            });
-        }
-    }
-
-    @VisibleForTesting
-    Fingerprint21(@NonNull Context context,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler,
-            @NonNull Handler handler,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull HalResultController controller,
-            @NonNull BiometricContext biometricContext) {
-        mContext = context;
-        mBiometricStateCallback = biometricStateCallback;
-        mAuthenticationStateListeners = authenticationStateListeners;
-        mBiometricContext = biometricContext;
-
-        mSensorProperties = sensorProps;
-        mSensorId = sensorProps.sensorId;
-        mIsUdfps = sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
-                || sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
-        mIsPowerbuttonFps = sensorProps.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON;
-
-        mScheduler = scheduler;
-        mHandler = handler;
-        mActivityTaskManager = ActivityTaskManager.getInstance();
-        mTaskStackListener = new BiometricTaskStackListener();
-        mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
-        mLazyDaemon = Fingerprint21.this::getDaemon;
-        mLockoutResetDispatcher = lockoutResetDispatcher;
-        mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
-        mHalResultController = controller;
-        mHalResultController.setCallback(() -> {
-            mDaemon = null;
-            mCurrentUserId = UserHandle.USER_NULL;
-        });
-
-        AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
-                new AuthenticationStatsBroadcastReceiver(
-                        mContext,
-                        BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                        (AuthenticationStatsCollector collector) -> {
-                            Slog.d(TAG, "Initializing AuthenticationStatsCollector");
-                            mAuthenticationStatsCollector = collector;
-                        });
-
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to register user switch observer");
-        }
-    }
-
-    public static Fingerprint21 newInstance(@NonNull Context context,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull Handler handler,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        final BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler =
-                new BiometricScheduler<>(
-                        BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
-                        gestureAvailabilityDispatcher);
-        final HalResultController controller = new HalResultController(sensorProps.sensorId,
-                context, handler, scheduler);
-        return new Fingerprint21(context, biometricStateCallback, authenticationStateListeners,
-                sensorProps, scheduler, handler, lockoutResetDispatcher, controller,
-                BiometricContext.getInstance(context));
-    }
-
-    @Override
-    public void serviceDied(long cookie) {
-        Slog.e(TAG, "HAL died");
-        mHandler.post(() -> {
-            PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId)
-                    .incrementHALDeathCount();
-            mDaemon = null;
-            mCurrentUserId = UserHandle.USER_NULL;
-
-            final BaseClientMonitor client = mScheduler.getCurrentClient();
-            if (client instanceof ErrorConsumer) {
-                Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                        0 /* vendorCode */);
-
-                FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
-                        BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                        BiometricsProtoEnums.ISSUE_HAL_DEATH,
-                        -1 /* sensorId */);
-            }
-
-            mScheduler.recordCrashState();
-            mScheduler.reset();
-        });
-    }
-
-    synchronized AidlSession getSession() {
-        if (mDaemon != null && mSession != null) {
-            return mSession;
-        } else {
-            return mSession = new AidlSession(this::getDaemon,
-                    mCurrentUserId, new AidlResponseHandler(mContext,
-                    mScheduler, mSensorId, mCurrentUserId, new LockoutCache(),
-                    mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> {
-                        mDaemon = null;
-                        mCurrentUserId = UserHandle.USER_NULL;
-                    }));
-        }
-    }
-
-    @VisibleForTesting
-    synchronized IBiometricsFingerprint getDaemon() {
-        if (mTestHalEnabled) {
-            final TestHal testHal = new TestHal(mContext, mSensorId);
-            testHal.setNotify(mHalResultController);
-            return testHal;
-        }
-
-        if (mDaemon != null) {
-            return mDaemon;
-        }
-
-        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
-                + mScheduler.getCurrentClient());
-        try {
-            mDaemon = IBiometricsFingerprint.getService();
-        } catch (java.util.NoSuchElementException e) {
-            // Service doesn't exist or cannot be opened.
-            Slog.w(TAG, "NoSuchElementException", e);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to get fingerprint HAL", e);
-        }
-
-        if (mDaemon == null) {
-            Slog.w(TAG, "Fingerprint HAL not available");
-            return null;
-        }
-
-        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
-        // HAL ID for these HIDL versions are only used to determine if callbacks have been
-        // successfully set.
-        long halId = 0;
-        try {
-            halId = mDaemon.setNotify(mHalResultController);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
-            mDaemon = null;
-        }
-
-        Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
-        if (halId != 0) {
-            scheduleLoadAuthenticatorIds();
-            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
-        } else {
-            Slog.e(TAG, "Unable to set callback");
-            mDaemon = null;
-        }
-
-        return mDaemon;
-    }
-
-    @Nullable IUdfpsOverlayController getUdfpsOverlayController() {
-        return mUdfpsOverlayController;
-    }
-
-    private void scheduleLoadAuthenticatorIds() {
-        // Note that this can be performed on the scheduler (as opposed to being done immediately
-        // when the HAL is (re)loaded, since
-        // 1) If this is truly the first time it's being performed (e.g. system has just started),
-        //    this will be run very early and way before any applications need to generate keys.
-        // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
-        //    just been reloaded), the framework already has a cache of the authenticatorIds. This
-        //    is safe because authenticatorIds only change when A) new template has been enrolled,
-        //    or B) all templates are removed.
-        mHandler.post(() -> {
-            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
-                final int targetUserId = user.id;
-                if (!mAuthenticatorIds.containsKey(targetUserId)) {
-                    scheduleUpdateActiveUserWithoutHandler(targetUserId, true /* force */);
-                }
-            }
-        });
-    }
-
-    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
-        scheduleUpdateActiveUserWithoutHandler(targetUserId, false /* force */);
-    }
-
-    /**
-     * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
-     * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
-     * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
-     * this operation on the same lambda/runnable as those operations so that the ordering is
-     * correct.
-     *
-     * @param targetUserId Switch to this user, and update their authenticatorId
-     * @param force Always retrieve the authenticatorId, even if we are already the targetUserId
-     */
-    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
-        final boolean hasEnrolled =
-                !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
-        final FingerprintUpdateActiveUserClientLegacy client =
-                new FingerprintUpdateActiveUserClientLegacy(mContext, mLazyDaemon, targetUserId,
-                        mContext.getOpPackageName(), mSensorProperties.sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext,
-                        this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                if (success) {
-                    if (mCurrentUserId != targetUserId) {
-                        // Create new session with updated user ID
-                        mSession = null;
-                    }
-                    mCurrentUserId = targetUserId;
-                } else {
-                    Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
-                }
-            }
-        });
-    }
-
-    private int getCurrentUser() {
-        return mCurrentUserId;
-    }
-
-    @Override
-    public boolean containsSensor(int sensorId) {
-        return mSensorProperties.sensorId == sensorId;
-    }
-
-    @Override
-    @NonNull
-    public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
-        final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
-        properties.add(mSensorProperties);
-        return properties;
-    }
-
-    @Nullable
-    @Override
-    public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
-        return mSensorProperties;
-    }
-
-    @Override
-    public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
-        // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
-        // thread.
-        mHandler.post(() -> {
-            if (Flags.deHidl()) {
-                scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken);
-            } else {
-                scheduleResetLockoutHidl(sensorId, userId);
-            }
-        });
-    }
-
-    private void scheduleResetLockoutAidl(int sensorId, int userId,
-            @Nullable byte[] hardwareAuthToken) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient(
-                        mContext, this::getSession, userId, mContext.getOpPackageName(),
-                        sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext, hardwareAuthToken, mLockoutTracker,
-                        mLockoutResetDispatcher,
-                        Utils.getCurrentStrength(sensorId));
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleResetLockoutHidl(int sensorId, int userId) {
-        final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
-                userId, mContext.getOpPackageName(), sensorId,
-                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN,
-                        mAuthenticationStatsCollector),
-                mBiometricContext, mLockoutTracker);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    @Override
-    public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            if (Flags.deHidl()) {
-                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
-            } else {
-                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient(
-                        mContext, this::getSession, token,
-                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                        mSensorProperties.sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
-        final FingerprintGenerateChallengeClient client =
-                new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
-                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                        mSensorProperties.sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    @Override
-    public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
-            @NonNull String opPackageName, long challenge) {
-        mHandler.post(() -> {
-            if (Flags.deHidl()) {
-                scheduleRevokeChallengeAidl(userId, token, opPackageName);
-            } else {
-                scheduleRevokeChallengeHidl(userId, token, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
-            @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient(
-                        mContext, this::getSession,
-                        token, userId, opPackageName,
-                        mSensorProperties.sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext, 0L);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
-            @NonNull String opPackageName) {
-        final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
-                mContext, mLazyDaemon, token, userId, opPackageName,
-                mSensorProperties.sensorId,
-                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN,
-                        mAuthenticationStatsCollector),
-                mBiometricContext);
-        mScheduler.scheduleClientMonitor(client);
-    }
-
-    @Override
-    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
-            @NonNull byte[] hardwareAuthToken, int userId,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            @FingerprintManager.EnrollReason int enrollReason,
-            @NonNull FingerprintEnrollOptions options) {
-        final long id = mRequestCounter.incrementAndGet();
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            if (Flags.deHidl()) {
-                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
-                        opPackageName, enrollReason, id, options);
-            } else {
-                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
-                        opPackageName, enrollReason, id, options);
-            }
-        });
-        return id;
-    }
-
-    private void scheduleEnrollHidl(@NonNull IBinder token,
-            @NonNull byte[] hardwareAuthToken, int userId,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            @FingerprintManager.EnrollReason int enrollReason, long id,
-            @NonNull FingerprintEnrollOptions options) {
-        final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
-                userId, hardwareAuthToken, opPackageName,
-                FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
-                mSensorProperties.sensorId,
-                createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, mUdfpsOverlayController,
-                // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-                mSidefpsController,
-                mAuthenticationStateListeners, enrollReason, options);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                mBiometricStateCallback.onClientStarted(clientMonitor);
-            }
-
-            @Override
-            public void onBiometricAction(int action) {
-                mBiometricStateCallback.onBiometricAction(action);
-            }
-
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
-                            true /* force */);
-                }
-            }
-        });
-    }
-
-    private void scheduleEnrollAidl(@NonNull IBinder token,
-            @NonNull byte[] hardwareAuthToken, int userId,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            @FingerprintManager.EnrollReason int enrollReason, long id,
-            @NonNull FingerprintEnrollOptions options) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
-                client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
-                        mContext,
-                        this::getSession, token, id,
-                        new ClientMonitorCallbackConverter(receiver),
-                        userId, hardwareAuthToken, opPackageName,
-                        FingerprintUtils.getLegacyInstance(mSensorId),
-                        mSensorProperties.sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext, null /* sensorProps */,
-                        mUdfpsOverlayController,
-                        // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-                        mSidefpsController,
-                        mAuthenticationStateListeners,
-                        mContext.getResources().getInteger(
-                                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
-                        enrollReason, options);
-        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                mBiometricStateCallback.onClientStarted(clientMonitor);
-            }
-
-            @Override
-            public void onBiometricAction(int action) {
-                mBiometricStateCallback.onBiometricAction(action);
-            }
-
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
-                            true /* force */);
-                }
-            }
-        });
-    }
-
-    @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
-    }
-
-    @Override
-    public long scheduleFingerDetect(@NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            int statsClient) {
-        final long id = mRequestCounter.incrementAndGet();
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(options.getUserId());
-
-            final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-
-            if (Flags.deHidl()) {
-                scheduleFingerDetectAidl(token, listener, options, statsClient, id,
-                        isStrongBiometric);
-            } else {
-                scheduleFingerDetectHidl(token, listener, options, statsClient, id,
-                        isStrongBiometric);
-            }
-        });
-
-        return id;
-    }
-
-    private void scheduleFingerDetectHidl(@NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            int statsClient, long id, boolean isStrongBiometric) {
-        final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                mLazyDaemon, token, id, listener, options,
-                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                        mAuthenticationStatsCollector),
-                mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    private void scheduleFingerDetectAidl(@NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            int statsClient, long id, boolean isStrongBiometric) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient
-                client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient(
-                        mContext,
-                        this::getSession, token, id, listener, options,
-                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    @Override
-    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            long requestId, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(options.getUserId());
-
-            final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-
-            if (Flags.deHidl()) {
-                scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId,
-                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
-            } else {
-                scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId,
-                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
-            }
-        });
-    }
-
-    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            long requestId, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient
-                client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient(
-                        mContext, this::getSession, token, requestId, listener, operationId,
-                        restricted, options, cookie, false /* requireConfirmation */,
-                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext, isStrongBiometric,
-                        mTaskStackListener,
-                        mUdfpsOverlayController, mSidefpsController,
-                        mAuthenticationStateListeners,
-                        allowBackgroundAuthentication, mSensorProperties, mHandler,
-                        Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            long requestId, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
-        final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                mContext, mLazyDaemon, token, requestId, listener, operationId,
-                restricted, options, cookie, false /* requireConfirmation */,
-                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                        mAuthenticationStatsCollector),
-                mBiometricContext, isStrongBiometric,
-                mTaskStackListener, mLockoutTracker,
-                mUdfpsOverlayController, mSidefpsController,
-                mAuthenticationStateListeners,
-                allowBackgroundAuthentication, mSensorProperties,
-                Utils.getCurrentStrength(mSensorId));
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    @Override
-    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
-            int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication) {
-        final long id = mRequestCounter.incrementAndGet();
-
-        scheduleAuthenticate(token, operationId, cookie, listener,
-                options, id, restricted, statsClient, allowBackgroundAuthentication);
-
-        return id;
-    }
-
-    @Override
-    public void startPreparedClient(int sensorId, int cookie) {
-        mHandler.post(() -> mScheduler.startPreparedClient(cookie));
-    }
-
-    @Override
-    public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
-        Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
-        mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
-    }
-
-    @Override
-    public void scheduleRemove(int sensorId, @NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
-            @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            if (Flags.deHidl()) {
-                scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName);
-            } else {
-                scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleRemoveHidl(@NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
-            @NonNull String opPackageName) {
-        final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
-                userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                mSensorProperties.sensorId,
-                createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    private void scheduleRemoveAidl(@NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
-            @NonNull String opPackageName) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient(
-                        mContext, this::getSession, token,
-                        new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId,
-                        opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                        mBiometricContext, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
-    }
-
-    @Override
-    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
-            @NonNull IFingerprintServiceReceiver receiver, int userId,
-            @NonNull String opPackageName) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
-            if (Flags.deHidl()) {
-                scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
-            } else {
-                scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
-            }
-        });
-    }
-
-    private void scheduleInternalCleanup(int userId,
-            @Nullable ClientMonitorCallback callback) {
-        mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
-
-            if (Flags.deHidl()) {
-                scheduleInternalCleanupAidl(userId, callback);
-            } else {
-                scheduleInternalCleanupHidl(userId, callback);
-            }
-        });
-    }
-
-    private void scheduleInternalCleanupHidl(int userId,
-            @Nullable ClientMonitorCallback callback) {
-        final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
-                mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
-                mSensorProperties.sensorId,
-                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext,
-                FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, callback);
-    }
-
-    private void scheduleInternalCleanupAidl(int userId,
-            @Nullable ClientMonitorCallback callback) {
-        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient
-                client =
-                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient(
-                        mContext, this::getSession, userId, mContext.getOpPackageName(),
-                        mSensorProperties.sensorId,
-                        createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                mAuthenticationStatsCollector),
-                        mBiometricContext,
-                        FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, callback);
-    }
-
-    @Override
-    public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable ClientMonitorCallback callback) {
-        scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
-                mBiometricStateCallback));
-    }
-
-    @Override
-    public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
-        scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
-                mBiometricStateCallback));
-    }
-
-    private BiometricLogger createLogger(int statsAction, int statsClient,
-            AuthenticationStatsCollector authenticationStatsCollector) {
-        return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient, authenticationStatsCollector);
-    }
-
-    @Override
-    public boolean isHardwareDetected(int sensorId) {
-        return getDaemon() != null;
-    }
-
-    @Override
-    public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
-        mHandler.post(() -> {
-            FingerprintUtils.getLegacyInstance(mSensorId)
-                    .renameBiometricForUser(mContext, userId, fingerId, name);
-        });
-    }
-
-    @Override
-    @NonNull
-    public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
-        return FingerprintUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
-    }
-
-    @Override
-    public boolean hasEnrollments(int sensorId, int userId) {
-        return !getEnrolledFingerprints(sensorId, userId).isEmpty();
-    }
-
-    @Override
-    @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
-        return mLockoutTracker.getLockoutModeForUser(userId);
-    }
-
-    @Override
-    public long getAuthenticatorId(int sensorId, int userId) {
-        return mAuthenticatorIds.getOrDefault(userId, 0L);
-    }
-
-    @Override
-    public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
-        mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
-            if (!(client instanceof Udfps)) {
-                Slog.w(TAG, "onFingerDown received during client: " + client);
-                return;
-            }
-            ((Udfps) client).onPointerDown(pc);
-        });
-    }
-
-    @Override
-    public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
-        mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
-            if (!(client instanceof Udfps)) {
-                Slog.w(TAG, "onFingerDown received during client: " + client);
-                return;
-            }
-            ((Udfps) client).onPointerUp(pc);
-        });
-    }
-
-    @Override
-    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
-            int sensorId) {
-        mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
-            if (!(client instanceof Udfps)) {
-                Slog.w(TAG, "onUdfpsUiEvent received during client: " + client);
-                return;
-            }
-            ((Udfps) client).onUdfpsUiEvent(event);
-        });
-    }
-
-    @Override
-    public void onPowerPressed() {
-        Slog.e(TAG, "onPowerPressed not supported for HIDL clients");
-    }
-
-    @Override
-    public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
-        mUdfpsOverlayController = controller;
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Override
-    public void setSidefpsController(@NonNull ISidefpsController controller) {
-        mSidefpsController = controller;
-    }
-
-    @Override
-    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
-            boolean clearSchedulerBuffer) {
-        final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
-
-        proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
-        proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT);
-        if (mSensorProperties.isAnyUdfpsType()) {
-            proto.write(SensorStateProto.MODALITY_FLAGS, SensorStateProto.FINGERPRINT_UDFPS);
-        }
-        proto.write(SensorStateProto.CURRENT_STRENGTH,
-                Utils.getCurrentStrength(mSensorProperties.sensorId));
-        proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
-
-        for (UserInfo user : UserManager.get(mContext).getUsers()) {
-            final int userId = user.getUserHandle().getIdentifier();
-
-            final long userToken = proto.start(SensorStateProto.USER_STATES);
-            proto.write(UserStateProto.USER_ID, userId);
-            proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getLegacyInstance(mSensorId)
-                    .getBiometricsForUser(mContext, userId).size());
-            proto.end(userToken);
-        }
-
-        proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
-                mSensorProperties.resetLockoutRequiresHardwareAuthToken);
-        proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
-                mSensorProperties.resetLockoutRequiresChallenge);
-
-        proto.end(sensorToken);
-    }
-
-    @Override
-    public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
-        PerformanceTracker tracker =
-                PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
-
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
-        for (UserInfo user : UserManager.get(mContext).getUsers()) {
-            final int userId = user.getUserHandle().getIdentifier();
-
-            final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
-
-            proto.write(FingerprintUserStatsProto.USER_ID, userId);
-            proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
-                    FingerprintUtils.getLegacyInstance(mSensorId)
-                            .getBiometricsForUser(mContext, userId).size());
-
-            // Normal fingerprint authentications (e.g. lockscreen)
-            long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
-            proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptForUser(userId));
-            proto.write(PerformanceStatsProto.REJECT, tracker.getRejectForUser(userId));
-            proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireForUser(userId));
-            proto.write(PerformanceStatsProto.LOCKOUT, tracker.getTimedLockoutForUser(userId));
-            proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT,
-                    tracker.getPermanentLockoutForUser(userId));
-            proto.end(countsToken);
-
-            // Statistics about secure fingerprint transactions (e.g. to unlock password
-            // storage, make secure purchases, etc.)
-            countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
-            proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptCryptoForUser(userId));
-            proto.write(PerformanceStatsProto.REJECT, tracker.getRejectCryptoForUser(userId));
-            proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireCryptoForUser(userId));
-            proto.write(PerformanceStatsProto.LOCKOUT, 0); // meaningless for crypto
-            proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, 0); // meaningless for crypto
-            proto.end(countsToken);
-
-            proto.end(userToken);
-        }
-        proto.flush();
-        tracker.clear();
-    }
-
-    @Override
-    public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
-            @NonNull IInvalidationCallback callback) {
-        // TODO (b/179101888): Remove this temporary workaround.
-        try {
-            callback.onCompleted();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to complete InvalidateAuthenticatorId");
-        }
-    }
-
-    @Override
-    public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
-        PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
-
-        JSONObject dump = new JSONObject();
-        try {
-            dump.put("service", TAG);
-            dump.put("isUdfps", mIsUdfps);
-            dump.put("isPowerbuttonFps", mIsPowerbuttonFps);
-
-            JSONArray sets = new JSONArray();
-            for (UserInfo user : UserManager.get(mContext).getUsers()) {
-                final int userId = user.getUserHandle().getIdentifier();
-                final int N = FingerprintUtils.getLegacyInstance(mSensorId)
-                        .getBiometricsForUser(mContext, userId).size();
-                JSONObject set = new JSONObject();
-                set.put("id", userId);
-                set.put("count", N);
-                set.put("accept", performanceTracker.getAcceptForUser(userId));
-                set.put("reject", performanceTracker.getRejectForUser(userId));
-                set.put("acquire", performanceTracker.getAcquireForUser(userId));
-                set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
-                set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
-                // cryptoStats measures statistics about secure fingerprint transactions
-                // (e.g. to unlock password storage, make secure purchases, etc.)
-                set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
-                set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
-                set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
-                sets.put(set);
-            }
-
-            dump.put("prints", sets);
-        } catch (JSONException e) {
-            Slog.e(TAG, "dump formatting failure", e);
-        }
-        pw.println(dump);
-        pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-        mScheduler.dump(pw);
-    }
-
-    void setTestHalEnabled(boolean enabled) {
-        mTestHalEnabled = enabled;
-    }
-
-    @NonNull
-    @Override
-    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
-            @NonNull String opPackageName) {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
-                mBiometricStateCallback, this, mHalResultController);
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
deleted file mode 100644
index f857946..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.trust.TrustManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.R;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * A mockable/testable provider of the {@link android.hardware.biometrics.fingerprint.V2_3} HIDL
- * interface. This class is intended simulate UDFPS logic for devices that do not have an actual
- * fingerprint@2.3 HAL (where UDFPS starts to become supported)
- *
- * UDFPS "accept" can only happen within a set amount of time after a sensor authentication. This is
- * specified by {@link MockHalResultController#AUTH_VALIDITY_MS}. Touches after this duration will
- * be treated as "reject".
- *
- * This class provides framework logic to emulate, for testing only, the UDFPS functionalies below:
- *
- * 1) IF either A) the caller is keyguard, and the device is not in a trusted state (authenticated
- *    via biometric sensor or unlocked with a trust agent {@see android.app.trust.TrustManager}, OR
- *    B) the caller is not keyguard, and regardless of trusted state, AND (following applies to both
- *    (A) and (B) above) {@link FingerprintManager#onFingerDown(int, int, float, float)} is
- *    received, this class will respond with {@link AuthenticationCallback#onAuthenticationFailed()}
- *    after a tunable flat_time + variance_time.
- *
- *    In the case above (1), callers must not receive a successful authentication event here because
- *    the sensor has not actually been authenticated.
- *
- * 2) IF A) the caller is keyguard and the device is not in a trusted state, OR B) the caller is not
- *    keyguard and regardless of trusted state, AND (following applies to both (A) and (B)) the
- *    sensor is touched and the fingerprint is accepted by the HAL, and then
- *    {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
- *    respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
- *    after a tunable flat_time + variance_time. Note that the authentication callback from the
- *    sensor is held until {@link FingerprintManager#onFingerDown(int, int, float, float)} is
- *    invoked.
- *
- *    In the case above (2), callers can receive a successful authentication callback because the
- *    real sensor was authenticated. Note that even though the real sensor was touched, keyguard
- *    fingerprint authentication does not put keyguard into a trusted state because the
- *    authentication callback is held until onFingerDown was invoked. This allows callers such as
- *    keyguard to simulate a realistic path.
- *
- * 3) IF the caller is keyguard AND the device in a trusted state and then
- *    {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
- *    respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
- *    after a tunable flat_time + variance time.
- *
- *    In the case above (3), since the device is already unlockable via trust agent, it's fine to
- *    simulate the successful auth path. Non-keyguard clients will fall into either (1) or (2)
- *    above.
- *
- *  This class currently does not simulate false rejection. Conversely, this class relies on the
- *  real hardware sensor so does not affect false acceptance.
- */
-@SuppressWarnings("deprecation")
-public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManager.TrustListener {
-
-    private static final String TAG = "Fingerprint21UdfpsMock";
-
-    // Secure setting integer. If true, the system will load this class to enable udfps testing.
-    public static final String CONFIG_ENABLE_TEST_UDFPS =
-            "com.android.server.biometrics.sensors.fingerprint.test_udfps.enable";
-    // Secure setting integer. A fixed duration intended to simulate something like the duration
-    // required for image capture.
-    private static final String CONFIG_AUTH_DELAY_PT1 =
-            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt1";
-    // Secure setting integer. A fixed duration intended to simulate something like the duration
-    // required for template matching to complete.
-    private static final String CONFIG_AUTH_DELAY_PT2 =
-            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt2";
-    // Secure setting integer. A random value between [-randomness, randomness] will be added to the
-    // capture delay above for each accept/reject.
-    private static final String CONFIG_AUTH_DELAY_RANDOMNESS =
-            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_randomness";
-
-    private static final int DEFAULT_AUTH_DELAY_PT1_MS = 300;
-    private static final int DEFAULT_AUTH_DELAY_PT2_MS = 400;
-    private static final int DEFAULT_AUTH_DELAY_RANDOMNESS_MS = 100;
-
-    @NonNull private final TestableBiometricScheduler mScheduler;
-    @NonNull private final Handler mHandler;
-    @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    @NonNull private final MockHalResultController mMockHalResultController;
-    @NonNull private final TrustManager mTrustManager;
-    @NonNull private final SparseBooleanArray mUserHasTrust;
-    @NonNull private final Random mRandom;
-    @NonNull private final FakeRejectRunnable mFakeRejectRunnable;
-    @NonNull private final FakeAcceptRunnable mFakeAcceptRunnable;
-    @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
-
-    private static class TestableBiometricScheduler extends BiometricScheduler {
-        @NonNull private Fingerprint21UdfpsMock mFingerprint21;
-
-        TestableBiometricScheduler(
-                @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-            super(BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
-        }
-
-        void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
-            mFingerprint21 = fingerprint21;
-        }
-    }
-
-    /**
-     * All of the mocking/testing should happen in here. This way we don't need to modify the
-     * {@link BaseClientMonitor} implementations and can run the
-     * real path there.
-     */
-    private static class MockHalResultController extends HalResultController {
-
-        // Duration for which a sensor authentication can be treated as UDFPS success.
-        private static final int AUTH_VALIDITY_MS = 10 * 1000; // 10 seconds
-
-        static class LastAuthArgs {
-            @NonNull final AuthenticationConsumer lastAuthenticatedClient;
-            final long deviceId;
-            final int fingerId;
-            final int groupId;
-            @Nullable final ArrayList<Byte> token;
-
-            LastAuthArgs(@NonNull AuthenticationConsumer authenticationConsumer, long deviceId,
-                    int fingerId, int groupId, @Nullable ArrayList<Byte> token) {
-                lastAuthenticatedClient = authenticationConsumer;
-                this.deviceId = deviceId;
-                this.fingerId = fingerId;
-                this.groupId = groupId;
-                if (token == null) {
-                    this.token = null;
-                } else {
-                    // Store a copy so the owner can be GC'd
-                    this.token = new ArrayList<>(token);
-                }
-            }
-        }
-
-        // Initialized after the constructor, but before it's ever used.
-        @NonNull private RestartAuthRunnable mRestartAuthRunnable;
-        @NonNull private Fingerprint21UdfpsMock mFingerprint21;
-        @Nullable private LastAuthArgs mLastAuthArgs;
-
-        MockHalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler scheduler) {
-            super(sensorId, context, handler, scheduler);
-        }
-
-        void init(@NonNull RestartAuthRunnable restartAuthRunnable,
-                @NonNull Fingerprint21UdfpsMock fingerprint21) {
-            mRestartAuthRunnable = restartAuthRunnable;
-            mFingerprint21 = fingerprint21;
-        }
-
-        @Nullable AuthenticationConsumer getLastAuthenticatedClient() {
-            return mLastAuthArgs != null ? mLastAuthArgs.lastAuthenticatedClient : null;
-        }
-
-        /**
-         * Intercepts the HAL authentication and holds it until the UDFPS simulation decides
-         * that authentication finished.
-         */
-        @Override
-        public void onAuthenticated(long deviceId, int fingerId, int groupId,
-                ArrayList<Byte> token) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(TAG, "Non authentication consumer: " + client);
-                    return;
-                }
-
-                final boolean accepted = fingerId != 0;
-                if (accepted) {
-                    mFingerprint21.setDebugMessage("Finger accepted");
-                } else {
-                    mFingerprint21.setDebugMessage("Finger rejected");
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                mLastAuthArgs = new LastAuthArgs(authenticationConsumer, deviceId, fingerId,
-                        groupId, token);
-
-                // Remove any existing restart runnbables since auth just started, and put a new
-                // one on the queue.
-                mHandler.removeCallbacks(mRestartAuthRunnable);
-                mRestartAuthRunnable.setLastAuthReference(authenticationConsumer);
-                mHandler.postDelayed(mRestartAuthRunnable, AUTH_VALIDITY_MS);
-            });
-        }
-
-        /**
-         * Calls through to the real interface and notifies clients of accept/reject.
-         */
-        void sendAuthenticated(long deviceId, int fingerId, int groupId,
-                ArrayList<Byte> token) {
-            Slog.d(TAG, "sendAuthenticated: " + (fingerId != 0));
-            mFingerprint21.setDebugMessage("Udfps match: " + (fingerId != 0));
-            super.onAuthenticated(deviceId, fingerId, groupId, token);
-        }
-    }
-
-    public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        Slog.d(TAG, "Creating Fingerprint23Mock!");
-
-        final Handler handler = new Handler(Looper.getMainLooper());
-        final TestableBiometricScheduler scheduler =
-                new TestableBiometricScheduler(gestureAvailabilityDispatcher);
-        final MockHalResultController controller =
-                new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
-        return new Fingerprint21UdfpsMock(context, biometricStateCallback,
-                authenticationStateListeners, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller, biometricContext);
-    }
-
-    private static abstract class FakeFingerRunnable implements Runnable {
-        private long mFingerDownTime;
-        private int mCaptureDuration;
-
-        /**
-         * @param fingerDownTime System time when onFingerDown occurred
-         * @param captureDuration Duration that the finger needs to be down for
-         */
-        void setSimulationTime(long fingerDownTime, int captureDuration) {
-            mFingerDownTime = fingerDownTime;
-            mCaptureDuration = captureDuration;
-        }
-
-        @SuppressWarnings("BooleanMethodIsAlwaysInverted")
-        boolean isImageCaptureComplete() {
-            return System.currentTimeMillis() - mFingerDownTime > mCaptureDuration;
-        }
-    }
-
-    private final class FakeRejectRunnable extends FakeFingerRunnable {
-        @Override
-        public void run() {
-            mMockHalResultController.sendAuthenticated(0, 0, 0, null);
-        }
-    }
-
-    private final class FakeAcceptRunnable extends FakeFingerRunnable {
-        @Override
-        public void run() {
-            if (mMockHalResultController.mLastAuthArgs == null) {
-                // This can happen if the user has trust agents enabled, which make lockscreen
-                // dismissable. Send a fake non-zero (accept) finger.
-                Slog.d(TAG, "Sending fake finger");
-                mMockHalResultController.sendAuthenticated(1 /* deviceId */,
-                        1 /* fingerId */, 1 /* groupId */, null /* token */);
-            } else {
-                mMockHalResultController.sendAuthenticated(
-                        mMockHalResultController.mLastAuthArgs.deviceId,
-                        mMockHalResultController.mLastAuthArgs.fingerId,
-                        mMockHalResultController.mLastAuthArgs.groupId,
-                        mMockHalResultController.mLastAuthArgs.token);
-            }
-        }
-    }
-
-    /**
-     * The fingerprint HAL allows multiple (5) fingerprint attempts per HIDL invocation of the
-     * authenticate method. However, valid fingerprint authentications are invalidated after
-     * {@link MockHalResultController#AUTH_VALIDITY_MS}, meaning UDFPS touches will be reported as
-     * rejects if touched after that duration. However, since a valid fingerprint was detected, the
-     * HAL and FingerprintService will not look for subsequent fingerprints.
-     *
-     * In order to keep the FingerprintManager API consistent (that multiple fingerprint attempts
-     * are allowed per auth lifecycle), we internally cancel and restart authentication so that the
-     * sensor is responsive again.
-     */
-    private static final class RestartAuthRunnable implements Runnable {
-        @NonNull private final Fingerprint21UdfpsMock mFingerprint21;
-        @NonNull private final TestableBiometricScheduler mScheduler;
-
-        // Store a reference to the auth consumer that should be invalidated.
-        private AuthenticationConsumer mLastAuthConsumer;
-
-        RestartAuthRunnable(@NonNull Fingerprint21UdfpsMock fingerprint21,
-                @NonNull TestableBiometricScheduler scheduler) {
-            mFingerprint21 = fingerprint21;
-            mScheduler = scheduler;
-        }
-
-        void setLastAuthReference(AuthenticationConsumer lastAuthConsumer) {
-            mLastAuthConsumer = lastAuthConsumer;
-        }
-
-        @Override
-        public void run() {
-            final BaseClientMonitor client = mScheduler.getCurrentClient();
-
-            // We don't care about FingerprintDetectClient, since accept/rejects are both OK. UDFPS
-            // rejects will just simulate the path where non-enrolled fingers are presented.
-            if (!(client instanceof FingerprintAuthenticationClient)) {
-                Slog.e(TAG, "Non-FingerprintAuthenticationClient client: " + client);
-                return;
-            }
-
-            // Perhaps the runnable is stale, or the user stopped/started auth manually. Do not
-            // restart auth in this case.
-            if (client != mLastAuthConsumer) {
-                Slog.e(TAG, "Current client: " + client
-                        + " does not match mLastAuthConsumer: " + mLastAuthConsumer);
-                return;
-            }
-
-            Slog.d(TAG, "Restarting auth, current: " + client);
-            mFingerprint21.setDebugMessage("Auth timed out");
-
-            final FingerprintAuthenticationClient authClient =
-                    (FingerprintAuthenticationClient) client;
-            // Store the authClient parameters so it can be rescheduled
-            final IBinder token = client.getToken();
-            final long operationId = authClient.getOperationId();
-            final int cookie = client.getCookie();
-            final ClientMonitorCallbackConverter listener = new ClientMonitorCallbackConverter(
-                    new IFingerprintServiceReceiver.Default());
-            final boolean restricted = authClient.isRestricted();
-            final int statsClient = client.getLogger().getStatsClient();
-            final boolean isKeyguard = authClient.isKeyguard();
-            final FingerprintAuthenticateOptions options =
-                    new FingerprintAuthenticateOptions.Builder()
-                            .setUserId(client.getTargetUserId())
-                            .setOpPackageName(client.getOwnerString())
-                            .build();
-
-            // Don't actually send cancel() to the HAL, since successful auth already finishes
-            // HAL authenticate() lifecycle. Just
-            mScheduler.getInternalCallback().onClientFinished(client, true /* success */);
-
-            // Schedule this only after we invoke onClientFinished for the previous client, so that
-            // internal preemption logic is not run.
-            mFingerprint21.scheduleAuthenticate(token,
-                    operationId, cookie, listener, options, restricted, statsClient,
-                    isKeyguard);
-        }
-    }
-
-    private Fingerprint21UdfpsMock(@NonNull Context context,
-            @NonNull BiometricStateCallback biometricStateCallback,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull TestableBiometricScheduler scheduler,
-            @NonNull Handler handler,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull MockHalResultController controller,
-            @NonNull BiometricContext biometricContext) {
-        super(context, biometricStateCallback, authenticationStateListeners, sensorProps, scheduler,
-                handler, lockoutResetDispatcher, controller, biometricContext);
-        mScheduler = scheduler;
-        mScheduler.init(this);
-        mHandler = handler;
-        // resetLockout is controlled by the framework, so hardwareAuthToken is not required
-        final boolean resetLockoutRequiresHardwareAuthToken = false;
-        final int maxTemplatesAllowed = mContext.getResources()
-                .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
-        mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
-                sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */,
-                resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
-        mMockHalResultController = controller;
-        mUserHasTrust = new SparseBooleanArray();
-        mTrustManager = context.getSystemService(TrustManager.class);
-        mTrustManager.registerTrustListener(this);
-        mRandom = new Random();
-        mFakeRejectRunnable = new FakeRejectRunnable();
-        mFakeAcceptRunnable = new FakeAcceptRunnable();
-        mRestartAuthRunnable = new RestartAuthRunnable(this, mScheduler);
-
-        // We can't initialize this during MockHalresultController's constructor due to a circular
-        // dependency.
-        mMockHalResultController.init(mRestartAuthRunnable, this);
-    }
-
-    @Override
-    public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
-            List<String> trustGrantedMessages) {
-        mUserHasTrust.put(userId, enabled);
-    }
-
-    @Override
-    public void onTrustManagedChanged(boolean enabled, int userId) {
-
-    }
-
-    @Override
-    public void onTrustError(CharSequence message) {
-
-    }
-
-    @Override
-    public void onEnabledTrustAgentsChanged(int userId) {
-
-    }
-
-    @Override
-    public void onIsActiveUnlockRunningChanged(boolean isRunning, int userId) {
-
-    }
-
-    @Override
-    @NonNull
-    public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
-        final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
-        properties.add(mSensorProperties);
-        return properties;
-    }
-
-    @Override
-    public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
-        mHandler.post(() -> {
-            Slog.d(TAG, "onFingerDown");
-            final AuthenticationConsumer lastAuthenticatedConsumer =
-                    mMockHalResultController.getLastAuthenticatedClient();
-            final BaseClientMonitor currentScheduledClient = mScheduler.getCurrentClient();
-
-            if (currentScheduledClient == null) {
-                Slog.d(TAG, "Not authenticating");
-                return;
-            }
-
-            mHandler.removeCallbacks(mFakeRejectRunnable);
-            mHandler.removeCallbacks(mFakeAcceptRunnable);
-
-            // The sensor was authenticated, is still the currently scheduled client, and the
-            // user touched the UDFPS affordance. Pretend that auth succeeded.
-            final boolean authenticatedClientIsCurrent = lastAuthenticatedConsumer != null
-                    && lastAuthenticatedConsumer == currentScheduledClient;
-            // User is unlocked on keyguard via Trust Agent
-            final boolean keyguardAndTrusted;
-            if (currentScheduledClient instanceof FingerprintAuthenticationClient) {
-                keyguardAndTrusted = ((FingerprintAuthenticationClient) currentScheduledClient)
-                        .isKeyguard()
-                        && mUserHasTrust.get(currentScheduledClient.getTargetUserId(), false);
-            } else {
-                keyguardAndTrusted = false;
-            }
-
-            final int captureDuration = getNewCaptureDuration();
-            final int matchingDuration = getMatchingDuration();
-            final int totalDuration = captureDuration + matchingDuration;
-            setDebugMessage("Duration: " + totalDuration
-                    + " (" + captureDuration + " + " + matchingDuration + ")");
-            if (authenticatedClientIsCurrent || keyguardAndTrusted) {
-                mFakeAcceptRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
-                mHandler.postDelayed(mFakeAcceptRunnable, totalDuration);
-            } else if (currentScheduledClient instanceof AuthenticationConsumer) {
-                // Something is authenticating but authentication has not succeeded yet. Pretend
-                // that auth rejected.
-                mFakeRejectRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
-                mHandler.postDelayed(mFakeRejectRunnable, totalDuration);
-            }
-        });
-    }
-
-    @Override
-    public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
-        mHandler.post(() -> {
-            Slog.d(TAG, "onFingerUp");
-
-            // Only one of these can be on the handler at any given time (see onFingerDown). If
-            // image capture is not complete, send ACQUIRED_TOO_FAST and remove the runnable from
-            // the handler. Image capture (onFingerDown) needs to happen again.
-            if (mHandler.hasCallbacks(mFakeRejectRunnable)
-                    && !mFakeRejectRunnable.isImageCaptureComplete()) {
-                mHandler.removeCallbacks(mFakeRejectRunnable);
-                mMockHalResultController.onAcquired(0 /* deviceId */,
-                        FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
-                        0 /* vendorCode */);
-            } else if (mHandler.hasCallbacks(mFakeAcceptRunnable)
-                    && !mFakeAcceptRunnable.isImageCaptureComplete()) {
-                mHandler.removeCallbacks(mFakeAcceptRunnable);
-                mMockHalResultController.onAcquired(0 /* deviceId */,
-                        FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
-                        0 /* vendorCode */);
-            }
-        });
-    }
-
-    private int getNewCaptureDuration() {
-        final ContentResolver contentResolver = mContext.getContentResolver();
-        final int captureTime = Settings.Secure.getIntForUser(contentResolver,
-                CONFIG_AUTH_DELAY_PT1,
-                DEFAULT_AUTH_DELAY_PT1_MS,
-                UserHandle.USER_CURRENT);
-        final int randomDelayRange = Settings.Secure.getIntForUser(contentResolver,
-                CONFIG_AUTH_DELAY_RANDOMNESS,
-                DEFAULT_AUTH_DELAY_RANDOMNESS_MS,
-                UserHandle.USER_CURRENT);
-        final int randomDelay = mRandom.nextInt(randomDelayRange * 2) - randomDelayRange;
-
-        // Must be at least 0
-        return Math.max(captureTime + randomDelay, 0);
-    }
-
-    private int getMatchingDuration() {
-        final int matchingTime = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                CONFIG_AUTH_DELAY_PT2,
-                DEFAULT_AUTH_DELAY_PT2_MS,
-                UserHandle.USER_CURRENT);
-
-        // Must be at least 0
-        return Math.max(matchingTime, 0);
-    }
-
-    private void setDebugMessage(String message) {
-        try {
-            final IUdfpsOverlayController controller = getUdfpsOverlayController();
-            // Things can happen before SysUI loads and sets the controller.
-            if (controller != null) {
-                Slog.d(TAG, "setDebugMessage: " + message);
-                controller.setDebugMessage(mSensorProperties.sensorId, message);
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when sending message: " + message, e);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
deleted file mode 100644
index b6311af..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.TaskStackListener;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.log.CallbackWithProbe;
-import com.android.server.biometrics.log.Probe;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific authentication client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintAuthenticationClient
-        extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions>
-        implements Udfps {
-
-    private static final String TAG = "Biometrics/FingerprintAuthClient";
-
-    private final LockoutFrameworkImpl mLockoutFrameworkImpl;
-    @NonNull private final SensorOverlays mSensorOverlays;
-    @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
-    @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
-    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
-
-    private boolean mIsPointerDown;
-
-    FingerprintAuthenticationClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
-            @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, long operationId,
-            boolean restricted, @NonNull FingerprintAuthenticateOptions options,
-            int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
-            @NonNull TaskStackListener taskStackListener,
-            @NonNull LockoutFrameworkImpl lockoutTracker,
-            @Nullable IUdfpsOverlayController udfpsOverlayController,
-            // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-            @Nullable ISidefpsController sidefpsController,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            boolean allowBackgroundAuthentication,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @Authenticators.Types int sensorStrength) {
-        super(context, lazyDaemon, token, listener, operationId, restricted,
-                options, cookie, requireConfirmation, logger, biometricContext,
-                isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
-                false /* shouldVibrate */, sensorStrength);
-        setRequestId(requestId);
-        mLockoutFrameworkImpl = lockoutTracker;
-        if (sidefpsControllerRefactor()) {
-            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
-        } else {
-            mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
-        }
-        mAuthenticationStateListeners = authenticationStateListeners;
-        mSensorProps = sensorProps;
-        mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-
-        if (mSensorProps.isAnyUdfpsType()) {
-            // UDFPS requires user to touch before becoming "active"
-            mState = STATE_STARTED_PAUSED;
-        } else {
-            mState = STATE_STARTED;
-        }
-    }
-
-    @NonNull
-    @Override
-    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
-    }
-
-    @Override
-    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
-            boolean authenticated, ArrayList<Byte> token) {
-        super.onAuthenticated(identifier, authenticated, token);
-
-        // Authentication lifecycle ends either when
-        // 1) Authenticated == true
-        // 2) Error occurred (lockout or some other error)
-        // Note that authentication doesn't end when Authenticated == false
-
-        if (authenticated) {
-            mState = STATE_STOPPED;
-            resetFailedAttempts(getTargetUserId());
-            mSensorOverlays.hide(getSensorId());
-            if (sidefpsControllerRefactor()) {
-                mAuthenticationStateListeners.onAuthenticationStopped();
-            }
-            if (reportBiometricAuthAttempts()) {
-                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
-                        getTargetUserId());
-            }
-        } else {
-            mState = STATE_STARTED_PAUSED_ATTEMPTED;
-            final @LockoutTracker.LockoutMode int lockoutMode =
-                    mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
-            if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
-                Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
-                final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
-                        ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
-                        : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-                // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
-                // controlled by the HAL, the framework must stop the sensor before finishing the
-                // client.
-                mSensorOverlays.hide(getSensorId());
-                if (sidefpsControllerRefactor()) {
-                    mAuthenticationStateListeners.onAuthenticationStopped();
-                }
-                onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
-                cancel();
-            }
-            if (reportBiometricAuthAttempts()) {
-                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
-                        getTargetUserId());
-            }
-        }
-    }
-
-    @Override
-    public void onError(int errorCode, int vendorCode) {
-        super.onError(errorCode, vendorCode);
-
-        if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) {
-            BiometricNotificationUtils.showBadCalibrationNotification(getContext());
-        }
-
-        mSensorOverlays.hide(getSensorId());
-        if (sidefpsControllerRefactor()) {
-            mAuthenticationStateListeners.onAuthenticationStopped();
-        }
-    }
-
-    private void resetFailedAttempts(int userId) {
-        mLockoutFrameworkImpl.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
-    }
-
-    @Override
-    protected void handleLifecycleAfterAuth(boolean authenticated) {
-        if (authenticated) {
-            mCallback.onClientFinished(this, true /* success */);
-        }
-    }
-
-    @Override
-    public void onAcquired(int acquiredInfo, int vendorCode) {
-        mAuthenticationStateListeners.onAuthenticationAcquired(
-                BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
-        super.onAcquired(acquiredInfo, vendorCode);
-
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                getLockoutTracker().getLockoutModeForUser(getTargetUserId());
-        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
-            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
-            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
-        }
-    }
-
-    @Override
-    public boolean wasUserDetected() {
-        // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
-        return false;
-    }
-
-    @Override
-    public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
-        mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                getLockoutTracker().getLockoutModeForUser(userId);
-        final PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(getSensorId());
-        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
-            performanceTracker.incrementPermanentLockoutForUser(userId);
-        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
-            performanceTracker.incrementTimedLockoutForUser(userId);
-        }
-
-        return lockoutMode;
-    }
-
-    @Override
-    protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getRequestReason(), this);
-        if (sidefpsControllerRefactor()) {
-            mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
-        }
-
-        try {
-            // GroupId was never used. In fact, groupId is always the same as userId.
-            getFreshDaemon().authenticate(mOperationId, getTargetUserId());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting auth", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mSensorOverlays.hide(getSensorId());
-            if (sidefpsControllerRefactor()) {
-                mAuthenticationStateListeners.onAuthenticationStopped();
-            }
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    protected void stopHalOperation() {
-        mSensorOverlays.hide(getSensorId());
-        if (sidefpsControllerRefactor()) {
-            mAuthenticationStateListeners.onAuthenticationStopped();
-        }
-
-        try {
-            getFreshDaemon().cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting cancel", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public void onPointerDown(PointerContext pc) {
-        mIsPointerDown = true;
-        mState = STATE_STARTED;
-        mALSProbeCallback.getProbe().enable();
-        UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
-
-        try {
-            getListener().onUdfpsPointerDown(getSensorId());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
-    }
-
-    @Override
-    public void onPointerUp(PointerContext pc) {
-        mIsPointerDown = false;
-        mState = STATE_STARTED_PAUSED_ATTEMPTED;
-        mALSProbeCallback.getProbe().disable();
-        UdfpsHelper.onFingerUp(getFreshDaemon());
-
-        try {
-            getListener().onUdfpsPointerUp(getSensorId());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
-    }
-
-    @Override
-    public boolean isPointerDown() {
-        return mIsPointerDown;
-    }
-
-    @Override
-    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
-        // Unsupported in HIDL.
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
deleted file mode 100644
index 50e48fe..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricRequestConstants;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
- * have the same haptic, lockout counter is not increased).
- */
-class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
-        implements AuthenticationConsumer, Udfps {
-
-    private static final String TAG = "FingerprintDetectClient";
-
-    private final boolean mIsStrongBiometric;
-    @NonNull private final SensorOverlays mSensorOverlays;
-    private boolean mIsPointerDown;
-
-    public FingerprintDetectClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
-            @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull FingerprintAuthenticateOptions options,
-            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
-            @Nullable IUdfpsOverlayController udfpsOverlayController,
-            boolean isStrongBiometric) {
-        super(context, lazyDaemon, token, listener, options.getUserId(),
-                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
-                true /* shouldVibrate */, biometricLogger, biometricContext);
-        setRequestId(requestId);
-        if (sidefpsControllerRefactor()) {
-            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
-        } else {
-            mSensorOverlays = new SensorOverlays(
-                    udfpsOverlayController, null /* sideFpsController */);
-        }
-        mIsStrongBiometric = isStrongBiometric;
-    }
-
-    @Override
-    protected void stopHalOperation() {
-        mSensorOverlays.hide(getSensorId());
-
-        try {
-            getFreshDaemon().cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting cancel", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-        startHalOperation();
-    }
-
-    @Override
-    protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
-                this);
-
-        try {
-            getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting auth", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mSensorOverlays.hide(getSensorId());
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public void onPointerDown(PointerContext pc) {
-        mIsPointerDown = true;
-        UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
-    }
-
-    @Override
-    public void onPointerUp(PointerContext pc) {
-        mIsPointerDown = false;
-        UdfpsHelper.onFingerUp(getFreshDaemon());
-    }
-
-    @Override
-    public boolean isPointerDown() {
-        return mIsPointerDown;
-    }
-
-    @Override
-    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
-        // Unsupported in HIDL.
-    }
-
-    @Override
-    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
-            boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
-        getLogger().logOnAuthenticated(getContext(), getOperationContext(),
-                authenticated, false /* requireConfirmation */,
-                getTargetUserId(), false /* isBiometricPrompt */);
-
-        // Do not distinguish between success/failures.
-        vibrateSuccess();
-
-        final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
-        pm.incrementAuthForUser(getTargetUserId(), authenticated);
-
-        try {
-            getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when sending onDetected", e);
-        }
-    }
-
-    @Override
-    public int getProtoEnum() {
-        return BiometricsProto.CM_DETECT_INTERACTION;
-    }
-
-    @Override
-    public boolean interruptsPrecedingClients() {
-        return true;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
deleted file mode 100644
index 8f937fc..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific enroll client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint>
-        implements Udfps {
-
-    private static final String TAG = "FingerprintEnrollClient";
-
-    @NonNull private final SensorOverlays mSensorOverlays;
-    private final @FingerprintManager.EnrollReason int mEnrollReason;
-    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
-    private boolean mIsPointerDown;
-
-    FingerprintEnrollClient(
-            @NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
-            @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull byte[] hardwareAuthToken, @NonNull String owner,
-            @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
-            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
-            @Nullable IUdfpsOverlayController udfpsOverlayController,
-            // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-            @Nullable ISidefpsController sidefpsController,
-            @NonNull AuthenticationStateListeners authenticationStateListeners,
-            @FingerprintManager.EnrollReason int enrollReason,
-            @NonNull FingerprintEnrollOptions options) {
-        super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
-                biometricContext,
-                BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason()));
-        setRequestId(requestId);
-        if (sidefpsControllerRefactor()) {
-            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
-        } else {
-            mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
-        }
-        mAuthenticationStateListeners = authenticationStateListeners;
-
-        mEnrollReason = enrollReason;
-        if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
-            getLogger().disableMetrics();
-        }
-        Slog.w(TAG, "EnrollOptions "
-                + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason()));
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-
-        BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
-    }
-
-    @NonNull
-    @Override
-    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
-        return new ClientMonitorCompositeCallback(
-                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
-    }
-
-    @Override
-    protected boolean hasReachedEnrollmentLimit() {
-        final int limit = getContext().getResources().getInteger(
-                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
-        final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
-                .size();
-        if (enrolled >= limit) {
-            Slog.w(TAG, "Too many fingerprints registered, user: " + getTargetUserId());
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
-                this);
-        if (sidefpsControllerRefactor()) {
-            mAuthenticationStateListeners.onAuthenticationStarted(
-                    getRequestReasonFromEnrollReason(mEnrollReason));
-        }
-
-        BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
-        try {
-            // GroupId was never used. In fact, groupId is always the same as userId.
-            getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting enroll", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mSensorOverlays.hide(getSensorId());
-            if (sidefpsControllerRefactor()) {
-                mAuthenticationStateListeners.onAuthenticationStopped();
-            }
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    protected void stopHalOperation() {
-        mSensorOverlays.hide(getSensorId());
-        if (sidefpsControllerRefactor()) {
-            mAuthenticationStateListeners.onAuthenticationStopped();
-        }
-
-        try {
-            getFreshDaemon().cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting cancel", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
-        super.onEnrollResult(identifier, remaining);
-
-        mSensorOverlays.ifUdfps(
-                controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
-
-        if (remaining == 0) {
-            mSensorOverlays.hide(getSensorId());
-            if (sidefpsControllerRefactor()) {
-                mAuthenticationStateListeners.onAuthenticationStopped();
-            }
-        }
-    }
-
-    @Override
-    public void onAcquired(int acquiredInfo, int vendorCode) {
-        super.onAcquired(acquiredInfo, vendorCode);
-
-        mSensorOverlays.ifUdfps(controller -> {
-            if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
-                controller.onEnrollmentHelp(getSensorId());
-            }
-        });
-
-        mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
-    }
-
-    @Override
-    public void onError(int errorCode, int vendorCode) {
-        super.onError(errorCode, vendorCode);
-
-        mSensorOverlays.hide(getSensorId());
-        if (sidefpsControllerRefactor()) {
-            mAuthenticationStateListeners.onAuthenticationStopped();
-        }
-    }
-
-    @Override
-    public void onPointerDown(PointerContext pc) {
-        mIsPointerDown = true;
-        UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
-    }
-
-    @Override
-    public void onPointerUp(PointerContext pc) {
-        mIsPointerDown = false;
-        UdfpsHelper.onFingerUp(getFreshDaemon());
-    }
-
-    @Override
-    public boolean isPointerDown() {
-        return mIsPointerDown;
-    }
-
-    @Override
-    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
-        // Unsupported in HIDL.
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
deleted file mode 100644
index 3bb7135..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific generateChallenge/preEnroll client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintGenerateChallengeClient
-        extends GenerateChallengeClient<IBiometricsFingerprint> {
-
-    private static final String TAG = "FingerprintGenerateChallengeClient";
-
-    FingerprintGenerateChallengeClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext) {
-        super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
-                biometricContext);
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            final long challenge = getFreshDaemon().preEnroll();
-            try {
-                getListener().onChallengeGenerated(getSensorId(), getTargetUserId(), challenge);
-                mCallback.onClientFinished(this, true /* success */);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
-                mCallback.onClientFinished(this, false /* success */);
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "preEnroll failed", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
deleted file mode 100644
index 8b61f59..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalCleanupClient;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintInternalCleanupClient
-        extends InternalCleanupClient<Fingerprint, IBiometricsFingerprint> {
-
-    FingerprintInternalCleanupClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull BiometricUtils<Fingerprint> utils,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                utils, authenticatorIds);
-    }
-
-    @Override
-    protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
-            Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
-            int userId, String owner, List<Fingerprint> enrolledList,
-            BiometricUtils<Fingerprint> utils, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
-        return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
-                enrolledList, utils, sensorId, logger, biometricContext);
-    }
-
-    @Override
-    protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
-            Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
-            int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
-            int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
-        // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
-        // is all done internally.
-        return new FingerprintRemovalClient(context, lazyDaemon, token,
-                null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, logger, biometricContext, authenticatorIds);
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
deleted file mode 100644
index 0840f1b..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFingerprint> {
-    private static final String TAG = "FingerprintInternalEnumerateClient";
-
-    FingerprintInternalEnumerateClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
-            @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
-        super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
-                logger, biometricContext);
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            getFreshDaemon().enumerate();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting enumerate", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
deleted file mode 100644
index 9ec56c2..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific removal client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFingerprint> {
-    private static final String TAG = "FingerprintRemovalClient";
-
-    private final int mBiometricId;
-
-    FingerprintRemovalClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
-            @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
-                logger, biometricContext, authenticatorIds);
-        mBiometricId = biometricId;
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            // GroupId was never used. In fact, groupId is always the same as userId.
-            getFreshDaemon().remove(getTargetUserId(), mBiometricId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when requesting remove", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
deleted file mode 100644
index 843fcc8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-
-/**
- * Clears lockout, which is handled in the framework (and not the HAL) for the
- * IBiometricsFingerprint@2.1 interface.
- */
-public class FingerprintResetLockoutClient extends BaseClientMonitor {
-
-    @NonNull final LockoutFrameworkImpl mLockoutTracker;
-
-    public FingerprintResetLockoutClient(@NonNull Context context, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull LockoutFrameworkImpl lockoutTracker) {
-        super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
-                sensorId, logger, biometricContext);
-        mLockoutTracker = lockoutTracker;
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-        mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
-                getTargetUserId());
-        callback.onClientFinished(this, true /* success */);
-    }
-
-    public boolean interruptsPrecedingClients() {
-        return true;
-    }
-
-    @Override
-    public int getProtoEnum() {
-        return BiometricsProto.CM_RESET_LOCKOUT;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
deleted file mode 100644
index 6273417..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific revokeChallenge client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintRevokeChallengeClient
-        extends RevokeChallengeClient<IBiometricsFingerprint> {
-
-    private static final String TAG = "FingerprintRevokeChallengeClient";
-
-    FingerprintRevokeChallengeClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            int userId, @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
-        super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            getFreshDaemon().postEnroll();
-            mCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "revokeChallenge/postEnroll failed", e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
deleted file mode 100644
index fc85402..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.Build;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.io.File;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * TODO(b/304604965): Delete this class once Flags.DE_HIDL is ready for release.
- */
-public class FingerprintUpdateActiveUserClientLegacy extends
-        HalClientMonitor<IBiometricsFingerprint> {
-    private static final String TAG = "FingerprintUpdateActiveUserClient";
-    private static final String FP_DATA_DIR = "fpdata";
-
-    private final Supplier<Integer> mCurrentUserId;
-    private final boolean mForceUpdateAuthenticatorId;
-    private final boolean mHasEnrolledBiometrics;
-    private final Map<Integer, Long> mAuthenticatorIds;
-    private File mDirectory;
-
-    FingerprintUpdateActiveUserClientLegacy(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull Supplier<Integer> currentUserId,
-            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
-            boolean forceUpdateAuthenticatorId) {
-        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
-        mCurrentUserId = currentUserId;
-        mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
-        mHasEnrolledBiometrics = hasEnrolledBiometrics;
-        mAuthenticatorIds = authenticatorIds;
-    }
-
-    @Override
-    public void start(@NonNull ClientMonitorCallback callback) {
-        super.start(callback);
-
-        if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
-            Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
-            callback.onClientFinished(this, true /* success */);
-            return;
-        }
-
-        int firstSdkInt = Build.VERSION.DEVICE_INITIAL_SDK_INT;
-        if (firstSdkInt < Build.VERSION_CODES.BASE) {
-            Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be "
-                    + "at least VERSION_CODES.BASE");
-        }
-        File baseDir;
-        if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
-            baseDir = Environment.getUserSystemDirectory(getTargetUserId());
-        } else {
-            baseDir = Environment.getDataVendorDeDirectory(getTargetUserId());
-        }
-
-        mDirectory = new File(baseDir, FP_DATA_DIR);
-        if (!mDirectory.exists()) {
-            if (!mDirectory.mkdir()) {
-                Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
-                callback.onClientFinished(this, false /* success */);
-                return;
-            }
-            // Calling mkdir() from this process will create a directory with our
-            // permissions (inherited from the containing dir). This command fixes
-            // the label.
-            if (!SELinux.restorecon(mDirectory)) {
-                Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
-                callback.onClientFinished(this, false /* success */);
-                return;
-            }
-        }
-
-        startHalOperation();
-    }
-
-    @Override
-    public void unableToStart() {
-        // Nothing to do here
-    }
-
-    @Override
-    protected void startHalOperation() {
-        try {
-            final int targetId = getTargetUserId();
-            Slog.d(TAG, "Setting active user: " + targetId);
-            getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
-            mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
-                    ? getFreshDaemon().getAuthenticatorId() : 0L);
-            mCallback.onClientFinished(this, true /* success */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to setActiveGroup: " + e);
-            mCallback.onClientFinished(this, false /* success */);
-        }
-    }
-
-    @Override
-    public int getProtoEnum() {
-        return BiometricsProto.CM_UPDATE_ACTIVE_USER;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 47fdcdb..3214b6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -186,7 +186,7 @@
                 mLockoutTracker,
                 mLockoutResetDispatcher,
                 mAuthSessionCoordinator,
-                () -> {}, mAidlResponseHandlerCallback);
+                mAidlResponseHandlerCallback);
     }
 
     @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 05e681e..645a366 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -61,6 +61,9 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -69,6 +72,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Range;
 import android.util.Slog;
 import android.view.Display;
 import android.view.IDisplayWindowListener;
@@ -85,6 +89,8 @@
 import com.android.server.SystemService;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -243,6 +249,7 @@
         public int mVideoStabilizationMode;
         public boolean mUsedUltraWide;
         public boolean mUsedZoomOverride;
+        public Range<Integer> mMostRequestedFpsRange;
         public final long mLogId;
         public final int mSessionIndex;
 
@@ -265,13 +272,15 @@
             mDeviceError = deviceError;
             mLogId = logId;
             mSessionIndex = sessionIdx;
+            mMostRequestedFpsRange = new Range<Integer>(0, 0);
         }
 
         public void markCompleted(int internalReconfigure, long requestCount,
                 long resultErrorCount, boolean deviceError,
                 List<CameraStreamStats>  streamStats, String userTag,
                 int videoStabilizationMode, boolean usedUltraWide,
-                boolean usedZoomOverride, CameraExtensionSessionStats extStats) {
+                boolean usedZoomOverride, Range<Integer> mostRequestedFpsRange,
+                CameraExtensionSessionStats extStats) {
             if (mCompleted) {
                 return;
             }
@@ -287,6 +296,7 @@
             mUsedUltraWide = usedUltraWide;
             mUsedZoomOverride = usedZoomOverride;
             mExtSessionStats = extStats;
+            mMostRequestedFpsRange = mostRequestedFpsRange;
             if (CameraServiceProxy.DEBUG) {
                 Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
                         " was in use by " + mClientName + " for " +
@@ -637,6 +647,60 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+                throws RemoteException {
+            new CSPShellCmd(CameraServiceProxy.this)
+                .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
+        private static class CSPShellCmd extends ShellCommand {
+            private static final String TAG = "CSPShellCmd";
+            private static final String USAGE = """
+                    usage: cmd media.camera.proxy SUBCMD [args]
+
+                    SUBCMDs:
+                        dump_events: Write out all collected camera usage events to statsd.
+                            Does not print to terminal.
+                        help: You're reading it.
+                    """;
+
+            private final CameraServiceProxy mCameraServiceProxy;
+
+            CSPShellCmd(CameraServiceProxy proxy) {
+                mCameraServiceProxy = proxy;
+            }
+
+            @Override
+            public int onCommand(String cmd) {
+                if (cmd == null) {
+                    return handleDefaultCommands(cmd);
+                }
+                final PrintWriter pw = getOutPrintWriter();
+                try {
+                    switch (cmd.replace('-', '_')) {
+                        case "dump_events":
+                            int eventCount = mCameraServiceProxy.getUsageEventCount();
+                            mCameraServiceProxy.dumpUsageEvents();
+                            pw.println("Camera usage events dumped: " + eventCount);
+                            break;
+                        default:
+                            return handleDefaultCommands(cmd);
+                    }
+                } catch (Exception e) {
+                    Slog.e(mCameraServiceProxy.TAG, "Error running shell command", e);
+                    return 1;
+                }
+                return 0;
+            }
+
+            @Override
+            public void onHelp() {
+                getOutPrintWriter().println(USAGE);
+            }
+        }
     };
 
     private final FoldStateListener mFoldStateListener;
@@ -882,6 +946,9 @@
                         ? ", zoomOverrideUsage " + e.mUsedZoomOverride
                         : "";
 
+                String mostRequestedFpsRangeDebug = Flags.analytics24q3()
+                        ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
+                        : "";
                 Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
                         + " clientName " + e.mClientName
                         + ", duration " + e.getDuration()
@@ -900,6 +967,7 @@
                         + ", videoStabilizationMode " + e.mVideoStabilizationMode
                         + ultrawideDebug
                         + zoomOverrideDebug
+                        + mostRequestedFpsRangeDebug
                         + ", logId " + e.mLogId
                         + ", sessionIndex " + e.mSessionIndex
                         + ", mExtSessionStats {type " + extensionType
@@ -966,7 +1034,17 @@
                     e.mUserTag, e.mVideoStabilizationMode,
                     e.mLogId, e.mSessionIndex,
                     extensionType, extensionIsAdvanced, e.mUsedUltraWide,
-                    e.mUsedZoomOverride);
+                    e.mUsedZoomOverride,
+                    e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+        }
+    }
+
+    /**
+     * Get camera usage event count
+     */
+    int getUsageEventCount() {
+        synchronized (mLock) {
+            return mCameraUsageHistory.size();
         }
     }
 
@@ -1173,6 +1251,10 @@
         long logId = cameraState.getLogId();
         int sessionIdx = cameraState.getSessionIndex();
         CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats();
+        Range<Integer> mostRequestedFpsRange = Flags.analytics24q3()
+                ? cameraState.getMostRequestedFpsRange()
+                : new Range<Integer>(0, 0);
+
         synchronized(mLock) {
             // Update active camera list and notify NFC if necessary
             boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1228,7 +1310,8 @@
                         oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
                                 /*resultErrorCount*/0, /*deviceError*/false, streamStats,
                                 /*userTag*/"", /*videoStabilizationMode*/-1, /*usedUltraWide*/false,
-                                /*usedZoomOverride*/false, new CameraExtensionSessionStats());
+                                /*usedZoomOverride*/false, new Range<Integer>(0, 0),
+                                new CameraExtensionSessionStats());
                         mCameraUsageHistory.add(oldEvent);
                     }
                     break;
@@ -1240,7 +1323,7 @@
                         doneEvent.markCompleted(internalReconfigureCount, requestCount,
                                 resultErrorCount, deviceError, streamStats, userTag,
                                 videoStabilizationMode, usedUltraWide, usedZoomOverride,
-                                extSessionStats);
+                                mostRequestedFpsRange, extSessionStats);
                         mCameraUsageHistory.add(doneEvent);
                         // Do not double count device error
                         deviceError = false;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f32c11d..73df594 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -250,8 +250,19 @@
     private final Object mAssociationsLock = new Object();
     @GuardedBy("mAssociationsLock")
     private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<>();
+
+    // The associations of input devices to displays by port. Maps from {InputDevice#mName} (String)
+    // 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
+    // input device go to a specific display.
+    @GuardedBy("mAssociationsLock")
+    private final Map<String, String> mUniqueIdAssociationsByDescriptor = new ArrayMap<>();
+
     // The map from input port (String) to the keyboard layout identifiers (comma separated string
     // containing language tag and layout type) associated with the corresponding keyboard device.
     // Currently only accessed by InputReader.
@@ -1741,8 +1752,8 @@
     /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
-     * @param inputPort The port of the input device.
-     * @param displayPort The physical port of the associated display.
+     * @param inputPort the port of the input device
+     * @param displayPort the physical port of the associated display
      */
     @Override // Binder call
     public void addPortAssociation(@NonNull String inputPort, int displayPort) {
@@ -1763,7 +1774,7 @@
     /**
      * Remove the runtime association between the input port and the display port. Any existing
      * static association for the cleared input port will be restored.
-     * @param inputPort The port of the input device to be cleared.
+     * @param inputPort the port of the input device to be cleared
      */
     @Override // Binder call
     public void removePortAssociation(@NonNull String inputPort) {
@@ -1782,10 +1793,11 @@
     }
 
     @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()")) {
+                "addUniqueIdAssociationByPort()")) {
             throw new SecurityException(
                     "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
@@ -1793,22 +1805,65 @@
         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()")) {
+                "removeUniqueIdAssociationByPort()")) {
             throw new SecurityException("Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
         }
 
         Objects.requireNonNull(inputPort);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.remove(inputPort);
+            mUniqueIdAssociationsByPort.remove(inputPort);
+        }
+        mNative.changeUniqueIdAssociation();
+    }
+
+    /**
+     * Adds a runtime association between the input device descriptor and the display unique id.
+     * @param inputDeviceDescriptor the descriptor of the input device
+     * @param displayUniqueId the unique ID of the display
+     */
+    @Override // Binder call
+    public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+                                                   @NonNull String displayUniqueId) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+                "addUniqueIdAssociationByDescriptor()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+        }
+
+        Objects.requireNonNull(inputDeviceDescriptor);
+        Objects.requireNonNull(displayUniqueId);
+        synchronized (mAssociationsLock) {
+            mUniqueIdAssociationsByDescriptor.put(inputDeviceDescriptor, displayUniqueId);
+        }
+        mNative.changeUniqueIdAssociation();
+    }
+
+    /**
+     * Removes the runtime association between the input device and the display.
+     * @param inputDeviceDescriptor the descriptor of the input device
+     */
+    @Override // Binder call
+    public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+                "removeUniqueIdAssociationByDescriptor()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+        }
+
+        Objects.requireNonNull(inputDeviceDescriptor);
+        synchronized (mAssociationsLock) {
+            mUniqueIdAssociationsByDescriptor.remove(inputDeviceDescriptor);
         }
         mNative.changeUniqueIdAssociation();
     }
@@ -2183,13 +2238,20 @@
                     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);
                 });
             }
+            if (!mUniqueIdAssociationsByDescriptor.isEmpty()) {
+                pw.println("Unique Id Associations:");
+                mUniqueIdAssociationsByDescriptor.forEach((k, v) -> {
+                    pw.print("  descriptor: " + k);
+                    pw.println("  uniqueId: " + v);
+                });
+            }
             if (!mDeviceTypeAssociations.isEmpty()) {
                 pw.println("Type Associations:");
                 mDeviceTypeAssociations.forEach((k, v) -> {
@@ -2622,10 +2684,21 @@
 
     // 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);
+    }
+
+    // Native callback
+    @SuppressWarnings("unused")
+    private String[] getInputUniqueIdAssociationsByDescriptor() {
+        final Map<String, String> associations;
+        synchronized (mAssociationsLock) {
+            associations = new HashMap<>(mUniqueIdAssociationsByDescriptor);
         }
 
         return flatten(associations);
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
@@ -67,7 +69,7 @@
         AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
     }
 
-    static void initialize(@NonNull Handler handler) {
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
         handler.post(() -> {
@@ -79,8 +81,16 @@
                             handler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     if (!sPerUserMap.contains(userId)) {
-                                        sPerUserMap.put(userId,
-                                                AdditionalSubtypeUtils.load(userId));
+                                        final AdditionalSubtypeMap additionalSubtypeMap =
+                                                AdditionalSubtypeUtils.load(userId);
+                                        sPerUserMap.put(userId, additionalSubtypeMap);
+                                        final InputMethodSettings settings =
+                                                InputMethodManagerService
+                                                        .queryInputMethodServicesInternal(context,
+                                                                userId,
+                                                                additionalSubtypeMap,
+                                                                DirectBootAwareness.AUTO);
+                                        InputMethodSettingsRepository.put(userId, settings);
                                     }
                                 }
                             });
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 03a85c4..2583d73 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -283,9 +283,12 @@
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
-    @NonNull
+
     @MultiUserUnawareField
-    private InputMethodSettings mSettings;
+    @UserIdInt
+    @GuardedBy("ImfLock.class")
+    private int mCurrentUserId;
+
     @MultiUserUnawareField
     final SettingsObserver mSettingsObserver;
     final WindowManagerInternal mWindowManagerInternal;
@@ -490,7 +493,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
-        return mSettings.getMethodMap().get(imeId);
+        return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
     }
 
     /**
@@ -811,7 +814,8 @@
                     InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
-                    String newEnabled = mSettings.getEnabledInputMethodsStr();
+                    String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+                            .getEnabledInputMethodsStr();
                     if (!mLastEnabled.equals(newEnabled)) {
                         mLastEnabled = newEnabled;
                         enabledChanged = true;
@@ -843,9 +847,11 @@
                 // sender userId can be a real user ID or USER_ALL.
                 final int senderUserId = pendingResult.getSendingUserId();
                 if (senderUserId != UserHandle.USER_ALL) {
-                    if (senderUserId != mSettings.getUserId()) {
-                        // A background user is trying to hide the dialog. Ignore.
-                        return;
+                    synchronized (ImfLock.class) {
+                        if (senderUserId != mCurrentUserId) {
+                            // A background user is trying to hide the dialog. Ignore.
+                            return;
+                        }
                     }
                 }
                 mMenuController.hideInputMethodMenu();
@@ -869,9 +875,14 @@
             if (!mSystemReady) {
                 return;
             }
-            mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                    AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                    DirectBootAwareness.AUTO);
+            for (int userId : mUserManagerInternal.getUserIds()) {
+                final InputMethodSettings settings = queryInputMethodServicesInternal(
+                                mContext,
+                                userId,
+                                AdditionalSubtypeMapRepository.get(userId),
+                                DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, settings);
+            }
             postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
             // If the locale is changed, needs to reset the default ime
             resetDefaultImeLocked(mContext);
@@ -932,7 +943,7 @@
         @GuardedBy("ImfLock.class")
         private boolean isChangingPackagesOfCurrentUserLocked() {
             final int userId = getChangingUserId();
-            final boolean retval = userId == mSettings.getUserId();
+            final boolean retval = userId == mCurrentUserId;
             if (DEBUG) {
                 if (!retval) {
                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -947,8 +958,10 @@
                 if (!isChangingPackagesOfCurrentUserLocked()) {
                     return false;
                 }
-                String curInputMethodId = mSettings.getSelectedInputMethod();
-                final List<InputMethodInfo> methodList = mSettings.getMethodList();
+                final InputMethodSettings settings =
+                        InputMethodSettingsRepository.get(mCurrentUserId);
+                String curInputMethodId = settings.getSelectedInputMethod();
+                final List<InputMethodInfo> methodList = settings.getMethodList();
                 final int numImes = methodList.size();
                 if (curInputMethodId != null) {
                     for (int i = 0; i < numImes; i++) {
@@ -1065,16 +1078,10 @@
         private void onFinishPackageChangesInternal() {
             synchronized (ImfLock.class) {
                 final int userId = getChangingUserId();
-                final boolean isCurrentUser = (userId == mSettings.getUserId());
+                final boolean isCurrentUser = (userId == mCurrentUserId);
                 final AdditionalSubtypeMap additionalSubtypeMap =
                         AdditionalSubtypeMapRepository.get(userId);
-                final InputMethodSettings settings;
-                if (isCurrentUser) {
-                    settings = mSettings;
-                } else {
-                    settings = queryInputMethodServicesInternal(mContext, userId,
-                            additionalSubtypeMap, DirectBootAwareness.AUTO);
-                }
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
 
                 InputMethodInfo curIm = null;
                 String curInputMethodId = settings.getSelectedInputMethod();
@@ -1118,16 +1125,17 @@
                     AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                             settings.getMethodMap());
                 }
+                if (isCurrentUser
+                        && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+                    return;
+                }
 
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
                 if (!isCurrentUser) {
                     return;
                 }
-
-                if (!(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
-                    return;
-                }
-                mSettings = queryInputMethodServicesInternal(mContext, userId,
-                        newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
 
                 boolean changed = false;
@@ -1281,21 +1289,20 @@
 
     void onUnlockUser(@UserIdInt int userId) {
         synchronized (ImfLock.class) {
-            final int currentUserId = mSettings.getUserId();
             if (DEBUG) {
-                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
-            }
-            if (userId != currentUserId) {
-                return;
+                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
             }
             if (!mSystemReady) {
                 return;
             }
-            mSettings = queryInputMethodServicesInternal(mContext, userId,
-                    AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
-            // We need to rebuild IMEs.
-            postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
-            updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+            final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                    userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+            InputMethodSettingsRepository.put(userId, newSettings);
+            if (mCurrentUserId == userId) {
+                // We need to rebuild IMEs.
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+                updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+            }
         }
     }
 
@@ -1361,19 +1368,20 @@
 
             mShowOngoingImeSwitcherForPhones = false;
 
-            AdditionalSubtypeMapRepository.initialize(mHandler);
+            // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+            InputMethodSettingsRepository.initialize(mHandler, mContext);
+            AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
 
-            final int userId = mActivityManagerInternal.getCurrentUserId();
+            mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
 
-            // mSettings should be created before buildInputMethodListLocked
-            mSettings = InputMethodSettings.createEmptyMap(userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
             mSwitchingController =
                     InputMethodSubtypeSwitchingController.createInstanceLocked(context,
-                            mSettings.getMethodMap(), userId);
+                            settings.getMethodMap(), settings.getUserId());
             mHardwareKeyboardShortcutController =
-                    new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
-                            mSettings.getUserId());
+                    new HardwareKeyboardShortcutController(settings.getMethodMap(),
+                            settings.getUserId());
             mMenuController = new InputMethodMenuController(this);
             mBindingController =
                     bindingControllerForTesting != null
@@ -1405,7 +1413,7 @@
     @GuardedBy("ImfLock.class")
     @UserIdInt
     int getCurrentImeUserIdLocked() {
-        return mSettings.getUserId();
+        return mCurrentUserId;
     }
 
     private final class InkWindowInitializer implements Runnable {
@@ -1441,12 +1449,13 @@
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
         String selectedMethodId = getSelectedMethodIdLocked();
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (selectedMethodId != null
-                && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
+                && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
             return;
         }
         final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
-                context, mSettings.getEnabledInputMethodList());
+                context, settings.getEnabledInputMethodList());
         if (suitableImes.isEmpty()) {
             Slog.i(TAG, "No default found");
             return;
@@ -1502,7 +1511,7 @@
             IInputMethodClientInvoker clientToBeReset) {
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
-                    + " currentUserId=" + mSettings.getUserId());
+                    + " currentUserId=" + mCurrentUserId);
         }
 
         maybeInitImeNavbarConfigLocked(newUserId);
@@ -1510,8 +1519,9 @@
         // ContentObserver should be registered again when the user is changed
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
-        mSettings = InputMethodSettings.createEmptyMap(newUserId);
-        final String defaultImiId = mSettings.getSelectedInputMethod();
+        mCurrentUserId = newUserId;
+        final String defaultImiId = SecureSettingsWrapper.getString(
+                Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
 
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
@@ -1529,10 +1539,9 @@
         // and user switch would not happen at that time.
         resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
 
-        mSettings = queryInputMethodServicesInternal(mContext, newUserId,
-                AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+        final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
         postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
-        if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+        if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
             // This is the first time of the user switch and
             // set the current ime to the proper one.
             resetDefaultImeLocked(mContext);
@@ -1542,12 +1551,12 @@
         if (initialUserSwitch) {
             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                     getPackageManagerForUser(mContext, newUserId),
-                    mSettings.getEnabledInputMethodList());
+                    newSettings.getEnabledInputMethodList());
         }
 
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
-                    + " selectedIme=" + mSettings.getSelectedInputMethod());
+                    + " selectedIme=" + newSettings.getSelectedInputMethod());
         }
 
         if (mIsInteractive && clientToBeReset != null) {
@@ -1570,7 +1579,7 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                final int currentUserId = mSettings.getUserId();
+                final int currentUserId = mCurrentUserId;
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
@@ -1591,7 +1600,7 @@
                     // the "mImeDrawsImeNavBarResLazyInitFuture" field.
                     synchronized (ImfLock.class) {
                         mImeDrawsImeNavBarResLazyInitFuture = null;
-                        if (currentUserId != mSettings.getUserId()) {
+                        if (currentUserId != mCurrentUserId) {
                             // This means that the current user is already switched to other user
                             // before the background task is executed. In this scenario the relevant
                             // field should already be initialized.
@@ -1610,17 +1619,19 @@
                         UserHandle.ALL, broadcastFilterForAllUsers, null, null,
                         Context.RECEIVER_EXPORTED);
 
-                final String defaultImiId = mSettings.getSelectedInputMethod();
+                final String defaultImiId = SecureSettingsWrapper.getString(
+                        Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-                mSettings = queryInputMethodServicesInternal(mContext, currentUserId,
-                        AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
                         DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(currentUserId, newSettings);
                 postInputMethodSettingUpdatedLocked(
                         !imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
-                        mSettings.getEnabledInputMethodList());
+                        newSettings.getEnabledInputMethodList());
             }
         }
     }
@@ -1667,7 +1678,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1690,7 +1701,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1718,14 +1729,12 @@
             }
 
             // Check if selected IME of current user supports handwriting.
-            if (userId == mSettings.getUserId()) {
+            if (userId == mCurrentUserId) {
                 return mBindingController.supportsStylusHandwriting()
                         && (!connectionless
                                 || mBindingController.supportsConnectionlessStylusHandwriting());
             }
-            //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
-            //TODO(b/210039666): use cache.
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
                     settings.getSelectedInputMethod());
             return imi != null && imi.supportsStylusHandwriting()
@@ -1749,9 +1758,8 @@
     private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness, int callingUid) {
         final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()
-                && directBootAwareness == DirectBootAwareness.AUTO) {
-            settings = mSettings;
+        if (directBootAwareness == DirectBootAwareness.AUTO) {
+            settings = InputMethodSettingsRepository.get(userId);
         } else {
             final AdditionalSubtypeMap additionalSubtypeMap =
                     AdditionalSubtypeMapRepository.get(userId);
@@ -1769,15 +1777,8 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
             int callingUid) {
-        final ArrayList<InputMethodInfo> methodList;
-        final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()) {
-            methodList = mSettings.getEnabledInputMethodList();
-            settings = mSettings;
-        } else {
-            settings = queryMethodMapForUserLocked(userId);
-            methodList = settings.getEnabledInputMethodList();
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
         // filter caller's access to input methods
         methodList.removeIf(imi ->
                 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -1835,22 +1836,7 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
-        if (userId == mSettings.getUserId()) {
-            final InputMethodInfo imi;
-            String selectedMethodId = getSelectedMethodIdLocked();
-            if (imiId == null && selectedMethodId != null) {
-                imi = mSettings.getMethodMap().get(selectedMethodId);
-            } else {
-                imi = mSettings.getMethodMap().get(imiId);
-            }
-            if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
-                return Collections.emptyList();
-            }
-            return mSettings.getEnabledInputMethodSubtypeList(
-                    imi, allowsImplicitlyEnabledSubtypes);
-        }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
         if (imi == null) {
             return Collections.emptyList();
@@ -2031,7 +2017,7 @@
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
-        final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
+        final StartInputInfo info = new StartInputInfo(mCurrentUserId,
                 getCurTokenLocked(),
                 mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
                 UserHandle.getUserId(mCurClient.mUid),
@@ -2045,9 +2031,9 @@
         // same-user scenarios.
         // That said ignoring cross-user scenario will never affect IMEs that do not have
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
-        if (mSettings.getUserId() == UserHandle.getUserId(
+        if (mCurrentUserId == UserHandle.getUserId(
                 mCurClient.mUid)) {
-            mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
+            mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
                     null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
                     mCurClient.mUid, true /* direct */);
         }
@@ -2070,7 +2056,8 @@
         }
 
         String curId = getCurIdLocked();
-        final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
+        final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+                .getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2258,17 +2245,18 @@
             return currentMethodId;
         }
 
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final int oldDeviceId = mDeviceIdToShowIme;
         mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
         if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
             if (oldDeviceId == DEVICE_ID_DEFAULT) {
                 return currentMethodId;
             }
-            final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+            final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
             if (DEBUG) {
                 Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
             }
-            mSettings.putSelectedDefaultDeviceInputMethod(null);
+            settings.putSelectedDefaultDeviceInputMethod(null);
             return defaultDeviceMethodId;
         }
 
@@ -2276,7 +2264,7 @@
                 mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
         if (Objects.equals(deviceMethodId, currentMethodId)) {
             return currentMethodId;
-        } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
+        } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
             if (DEBUG) {
                 Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
                         + " because its custom input method is not available: " + deviceMethodId);
@@ -2288,7 +2276,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Storing default device input method " + currentMethodId);
             }
-            mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+            settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
         }
         if (DEBUG) {
             Slog.v(TAG, "Switching current input method from " + currentMethodId
@@ -2318,7 +2306,8 @@
         if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
             return false;
         }
-        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+        final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+                .getMethodMap().get(selectedMethodId);
         if (imi == null) {
             return false;
         }
@@ -2662,7 +2651,7 @@
                 } else if (packageName != null) {
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     final PackageManager userAwarePackageManager =
-                            getPackageManagerForUser(mContext, mSettings.getUserId());
+                            getPackageManagerForUser(mContext, mCurrentUserId);
                     ApplicationInfo applicationInfo = null;
                     try {
                         applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -2724,7 +2713,7 @@
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
-                && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+                && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
             return false;
         }
         if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2741,7 +2730,8 @@
             return false;
         }
 
-        List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
                 InputMethodInfo::shouldShowInInputMethodPicker);
         final int numImes = imes.size();
         if (numImes > 2) return true;
@@ -2753,7 +2743,7 @@
         for (int i = 0; i < numImes; ++i) {
             final InputMethodInfo imi = imes.get(i);
             final List<InputMethodSubtype> subtypes =
-                    mSettings.getEnabledInputMethodSubtypeList(imi, true);
+                    settings.getEnabledInputMethodSubtypeList(imi, true);
             final int subtypeCount = subtypes.size();
             if (subtypeCount == 0) {
                 ++nonAuxCount;
@@ -2905,11 +2895,12 @@
 
     @GuardedBy("ImfLock.class")
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (enabledMayChange) {
             final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
-                    mSettings.getUserId());
+                    settings.getUserId());
 
-            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+            List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
             for (int i = 0; i < enabled.size(); i++) {
                 // We allow the user to select "disabled until used" apps, so if they
                 // are enabling one of those here we now need to make it enabled.
@@ -2936,20 +2927,20 @@
 
         if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
             String ime = SecureSettingsWrapper.getString(
-                    Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+                    Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId());
             String defaultDeviceIme = SecureSettingsWrapper.getString(
-                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
             if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
                 if (DEBUG) {
                     Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
-                            + " device input method for user " + mSettings.getUserId()
+                            + " device input method for user " + settings.getUserId()
                             + " - restoring " + defaultDeviceIme);
                 }
                 SecureSettingsWrapper.putString(
                         Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
-                        mSettings.getUserId());
+                        settings.getUserId());
                 SecureSettingsWrapper.putString(
-                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
             }
         }
 
@@ -2957,14 +2948,14 @@
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
         // enabled.
-        String id = mSettings.getSelectedInputMethod();
+        String id = settings.getSelectedInputMethod();
         // There is no input method selected, try to choose new applicable input method.
         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
-            id = mSettings.getSelectedInputMethod();
+            id = settings.getSelectedInputMethod();
         }
         if (!TextUtils.isEmpty(id)) {
             try {
-                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
                 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
@@ -2975,18 +2966,18 @@
         }
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+        if (settings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(settings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    mContext, settings.getMethodMap(), settings.getUserId());
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+        if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mSettings.getMethodMap(), mSettings.getUserId());
+                    settings.getMethodMap(), settings.getUserId());
         }
         sendOnNavButtonFlagsChangedLocked();
     }
@@ -3010,14 +3001,15 @@
 
     @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId, int deviceId) {
-        InputMethodInfo info = mSettings.getMethodMap().get(id);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        InputMethodInfo info = settings.getMethodMap().get(id);
         if (info == null) {
             throw getExceptionForUnknownImeId(id);
         }
 
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(getSelectedMethodIdLocked())) {
-            final int userId = mSettings.getUserId();
+            final int userId = settings.getUserId();
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3058,7 +3050,7 @@
             // method is a custom one specific to a virtual device. So only update the settings
             // entry used to restore the default device input method once we want to show the IME
             // back on the default device.
-            mSettings.putSelectedDefaultDeviceInputMethod(id);
+            settings.putSelectedDefaultDeviceInputMethod(id);
             return;
         }
         IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3586,7 +3578,7 @@
                             return InputBindResult.USER_SWITCHING;
                         }
                         final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
-                                mSettings.getUserId(), false /* enabledOnly */);
+                                mCurrentUserId, false /* enabledOnly */);
                         for (int profileId : profileIdsWithDisabled) {
                             if (profileId == userId) {
                                 scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3632,10 +3624,10 @@
                     }
 
                     // Verify if caller is a background user.
-                    final int currentUserId = mSettings.getUserId();
-                    if (userId != currentUserId) {
+                    if (userId != mCurrentUserId) {
                         if (ArrayUtils.contains(
-                                mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+                                mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+                                userId)) {
                             // cross-profile access is always allowed here to allow
                             // profile-switching.
                             scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3824,7 +3816,7 @@
                 && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
             return true;
         }
-        if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
+        if (mCurrentUserId != UserHandle.getUserId(uid)) {
             return false;
         }
         if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -3888,9 +3880,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                    imi.getPackageName(), callingUid, userId, settings)) {
                 throw getExceptionForUnknownImeId(id);
             }
             setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
@@ -3906,9 +3899,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                    imi.getPackageName(), callingUid, userId, settings)) {
                 throw getExceptionForUnknownImeId(id);
             }
             if (subtype != null) {
@@ -3926,10 +3920,11 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
             final InputMethodInfo lastImi;
             if (lastIme != null) {
-                lastImi = mSettings.getMethodMap().get(lastIme.first);
+                lastImi = settings.getMethodMap().get(lastIme.first);
             } else {
                 lastImi = null;
             }
@@ -3953,7 +3948,7 @@
                 // This is a safety net. If the currentSubtype can't be added to the history
                 // and the framework couldn't find the last ime, we will make the last ime be
                 // the most applicable enabled keyboard subtype of the system imes.
-                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+                final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
                 if (enabled != null) {
                     final int enabledCount = enabled.size();
                     final String locale;
@@ -3961,7 +3956,7 @@
                             && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
                         locale = mCurrentSubtype.getLocale();
                     } else {
-                        locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
+                        locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
                     }
                     for (int i = 0; i < enabledCount; ++i) {
                         final InputMethodInfo imi = enabled.get(i);
@@ -4008,8 +4003,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+                onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
                 mCurrentSubtype);
         if (nextSubtype == null) {
             return false;
@@ -4025,9 +4021,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
                     false /* onlyCurrentIme */,
-                    mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+                    settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
             return nextSubtype != null;
         }
     }
@@ -4039,12 +4036,7 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
-                return mSettings.getLastInputMethodSubtype();
-            }
-
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getLastInputMethodSubtype();
+            return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
         }
     }
 
@@ -4075,23 +4067,20 @@
             }
 
             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
-            final boolean isCurrentUser = (mSettings.getUserId() == userId);
-            final InputMethodSettings settings = isCurrentUser
-                    ? mSettings
-                    : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                            DirectBootAwareness.AUTO);
+            final boolean isCurrentUser = (mCurrentUserId == userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
                 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                         settings.getMethodMap());
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, AdditionalSubtypeMapRepository.get(userId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
                 if (isCurrentUser) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        mSettings = queryInputMethodServicesInternal(mContext,
-                                mSettings.getUserId(),
-                                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                                DirectBootAwareness.AUTO);
                         postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
@@ -4121,9 +4110,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (ImfLock.class) {
-                final boolean currentUser = (mSettings.getUserId() == userId);
-                final InputMethodSettings settings = currentUser
-                        ? mSettings : queryMethodMapForUserLocked(userId);
+                final boolean currentUser = (mCurrentUserId == userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -4465,11 +4453,11 @@
                 }
                 return;
             }
-            if (mSettings.getUserId() != mSwitchingController.getUserId()) {
+            if (mCurrentUserId != mSwitchingController.getUserId()) {
                 return;
             }
-            final InputMethodInfo imi =
-                    mSettings.getMethodMap().get(getSelectedMethodIdLocked());
+            final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+                    .getMethodMap().get(getSelectedMethodIdLocked());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -4529,8 +4517,9 @@
             return;
         } else {
             // Called with current IME's token.
-            if (mSettings.getMethodMap().get(id) != null
-                    && mSettings.getEnabledInputMethodListWithFilter(
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            if (settings.getMethodMap().get(id) != null
+                    && settings.getEnabledInputMethodListWithFilter(
                             (info) -> info.getId().equals(id)).isEmpty()) {
                 throw new IllegalStateException("Requested IME is not enabled: " + id);
             }
@@ -4709,21 +4698,23 @@
                         return false;
                 }
                 synchronized (ImfLock.class) {
+                    final InputMethodSettings settings =
+                            InputMethodSettingsRepository.get(mCurrentUserId);
                     final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
-                            && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
-                    final String lastInputMethodId = mSettings.getSelectedInputMethod();
+                            && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+                    final String lastInputMethodId = settings.getSelectedInputMethod();
                     int lastInputMethodSubtypeId =
-                            mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+                            settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
 
                     final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                             .getSortedInputMethodAndSubtypeList(
                                     showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
-                                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                                    mContext, settings.getMethodMap(), settings.getUserId());
                     if (imList.isEmpty()) {
                         Slog.w(TAG, "Show switching menu failed, imList is empty,"
                                 + " showAuxSubtypes: " + showAuxSubtypes
                                 + " isScreenLocked: " + isScreenLocked
-                                + " userId: " + mSettings.getUserId());
+                                + " userId: " + settings.getUserId());
                         return false;
                     }
 
@@ -4909,8 +4900,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean chooseNewDefaultIMELocked() {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
-                mSettings.getEnabledInputMethodList());
+                settings.getEnabledInputMethodList());
         if (imi != null) {
             if (DEBUG) {
                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -5024,6 +5016,8 @@
         mMethodMapUpdateCount++;
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
         // changed.
@@ -5034,7 +5028,7 @@
             final List<ResolveInfo> allInputMethodServices =
                     mContext.getPackageManager().queryIntentServicesAsUser(
                             new Intent(InputMethod.SERVICE_INTERFACE),
-                            PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
+                            PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
             final int numImes = allInputMethodServices.size();
             for (int i = 0; i < numImes; ++i) {
                 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5049,11 +5043,11 @@
         if (!resetDefaultEnabledIme) {
             boolean enabledImeFound = false;
             boolean enabledNonAuxImeFound = false;
-            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
+            final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
             final int numImes = enabledImes.size();
             for (int i = 0; i < numImes; ++i) {
                 final InputMethodInfo imi = enabledImes.get(i);
-                if (mSettings.getMethodMap().containsKey(imi.getId())) {
+                if (settings.getMethodMap().containsKey(imi.getId())) {
                     enabledImeFound = true;
                     if (!imi.isAuxiliaryIme()) {
                         enabledNonAuxImeFound = true;
@@ -5077,7 +5071,7 @@
 
         if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
-                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
+                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
                             reenableMinimumNonAuxSystemImes);
             final int numImes = defaultEnabledIme.size();
             for (int i = 0; i < numImes; ++i) {
@@ -5089,9 +5083,9 @@
             }
         }
 
-        final String defaultImiId = mSettings.getSelectedInputMethod();
+        final String defaultImiId = settings.getSelectedInputMethod();
         if (!TextUtils.isEmpty(defaultImiId)) {
-            if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
+            if (!settings.getMethodMap().containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
                 if (chooseNewDefaultIMELocked()) {
                     updateInputMethodsFromSettingsLocked(true);
@@ -5105,26 +5099,26 @@
         updateDefaultVoiceImeIfNeededLocked();
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+        if (settings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(settings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    mContext, settings.getMethodMap(), mCurrentUserId);
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+        if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mSettings.getMethodMap(), mSettings.getUserId());
+                    settings.getMethodMap(), settings.getUserId());
         }
 
         sendOnNavButtonFlagsChangedLocked();
 
         // Notify InputMethodListListeners of the new installed InputMethods.
-        final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
+        final List<InputMethodInfo> inputMethodList = settings.getMethodList();
         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
-                mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+                settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
     }
 
     @GuardedBy("ImfLock.class")
@@ -5139,11 +5133,12 @@
 
     @GuardedBy("ImfLock.class")
     private void updateDefaultVoiceImeIfNeededLocked() {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final String systemSpeechRecognizer =
                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
-        final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+        final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
         final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
-                mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
+                settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
         if (newSystemVoiceIme == null) {
             if (DEBUG) {
                 Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5152,7 +5147,7 @@
             // Clear DEFAULT_VOICE_INPUT_METHOD when necessary.  Note that InputMethodSettings
             // does not update the actual Secure Settings until the user is unlocked.
             if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
-                mSettings.putDefaultVoiceInputMethod("");
+                settings.putDefaultVoiceInputMethod("");
                 // We don't support disabling the voice ime when a package is removed from the
                 // config.
             }
@@ -5165,7 +5160,7 @@
             Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
         }
         setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
-        mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+        settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
     }
 
     // ----------------------------------------------------------------------
@@ -5180,8 +5175,9 @@
      */
     @GuardedBy("ImfLock.class")
     private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (enabled) {
-            final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr();
+            final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
             final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
                     enabledImeIdsStr, id);
             if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
@@ -5189,29 +5185,29 @@
                 // Nothing to do. The previous state was enabled.
                 return true;
             }
-            mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
+            settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
             // Previous state was disabled.
             return false;
         } else {
-            final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+            final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
                     .getEnabledInputMethodsAndSubtypeList();
             StringBuilder builder = new StringBuilder();
-            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
+            if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
                     builder, enabledInputMethodsList, id)) {
                 if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
                     // Disabled input method is currently selected, switch to another one.
-                    final String selId = mSettings.getSelectedInputMethod();
+                    final String selId = settings.getSelectedInputMethod();
                     if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                         Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                         resetSelectedInputMethodAndSubtypeLocked("");
                     }
-                } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+                } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
                     // Disabled default device IME while using a virtual device one, choose a
                     // new default one but only update the settings.
                     InputMethodInfo newDefaultIme =
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
-                                        mSettings.getEnabledInputMethodList());
-                    mSettings.putSelectedDefaultDeviceInputMethod(
+                                    settings.getEnabledInputMethodList());
+                    settings.putSelectedDefaultDeviceInputMethod(
                             newDefaultIme == null ? null : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
@@ -5227,29 +5223,30 @@
     @GuardedBy("ImfLock.class")
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
                 mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
-            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
             mCurrentSubtype = null;
         } else {
             if (subtypeId < imi.getSubtypeCount()) {
                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
-                mSettings.putSelectedSubtype(subtype.hashCode());
+                settings.putSelectedSubtype(subtype.hashCode());
                 mCurrentSubtype = subtype;
             } else {
-                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+                settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                 // If the subtype is not specified, choose the most applicable one
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
-        notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
+        notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
             // Set InputMethod here
-            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+            settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
         }
     }
 
@@ -5257,13 +5254,15 @@
     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
         mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
         mDisplayIdToShowIme = INVALID_DISPLAY;
-        mSettings.putSelectedDefaultDeviceInputMethod(null);
 
-        InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        settings.putSelectedDefaultDeviceInputMethod(null);
+
+        InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
         int lastSubtypeId = NOT_A_SUBTYPE_ID;
         // newDefaultIme is empty when there is no candidate for the selected IME.
         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
-            String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
+            String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
             if (subtypeHashCode != null) {
                 try {
                     lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5290,12 +5289,12 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
+            if (mCurrentUserId == userId) {
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+            return InputMethodSettingsRepository.get(userId)
+                    .getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
 
@@ -5315,26 +5314,27 @@
         if (selectedMethodId == null) {
             return null;
         }
-        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
-        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        final boolean subtypeIsSelected = settings.isSubtypeSelected();
+        final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
         if (imi == null || imi.getSubtypeCount() == 0) {
             return null;
         }
         if (!subtypeIsSelected || mCurrentSubtype == null
                 || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
-            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
+            int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId);
             if (subtypeId == NOT_A_SUBTYPE_ID) {
                 // If there are no selected subtypes, the framework will try to find
                 // the most applicable subtype from explicitly or implicitly enabled
                 // subtypes.
                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                        mSettings.getEnabledInputMethodSubtypeList(imi, true);
+                        settings.getEnabledInputMethodSubtypeList(imi, true);
                 // If there is only one explicitly or implicitly enabled subtype,
                 // just returns it.
                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
-                    final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
+                    final String locale = SystemLocaleWrapper.get(settings.getUserId())
                             .get(0).toString();
                     mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
                             explicitlyOrImplicitlyEnabledSubtypes,
@@ -5357,38 +5357,22 @@
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
-        final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()) {
-            settings = mSettings;
-        } else {
-            final AdditionalSubtypeMap additionalSubtypeMap =
-                    AdditionalSubtypeMapRepository.get(userId);
-            settings = queryInputMethodServicesInternal(mContext, userId,
-                    additionalSubtypeMap, DirectBootAwareness.AUTO);
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
     @GuardedBy("ImfLock.class")
-    private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
-        final AdditionalSubtypeMap additionalSubtypeMap =
-                AdditionalSubtypeMapRepository.get(userId);
-        return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                DirectBootAwareness.AUTO);
-    }
-
-    @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
-        if (userId == mSettings.getUserId()) {
-            if (!mSettings.getMethodMap().containsKey(imeId)
-                    || !mSettings.getEnabledInputMethodList()
-                    .contains(mSettings.getMethodMap().get(imeId))) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (userId == mCurrentUserId) {
+            if (!settings.getMethodMap().containsKey(imeId)
+                    || !settings.getEnabledInputMethodList()
+                    .contains(settings.getMethodMap().get(imeId))) {
                 return false; // IME is not found or not enabled.
             }
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5429,8 +5413,9 @@
 
     @GuardedBy("ImfLock.class")
     private void switchKeyboardLayoutLocked(int direction) {
-        final InputMethodInfo currentImi = mSettings.getMethodMap().get(
-                getSelectedMethodIdLocked());
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
+        final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
         if (currentImi == null) {
             return;
         }
@@ -5442,7 +5427,7 @@
         if (nextSubtypeHandle == null) {
             return;
         }
-        final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
+        final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
         if (nextImi == null) {
             return;
         }
@@ -5521,17 +5506,14 @@
         @Override
         public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                if (userId == mSettings.getUserId()) {
-                    if (!mSettings.getMethodMap().containsKey(imeId)) {
-                        return false; // IME is not found.
-                    }
-                    setInputMethodEnabledLocked(imeId, enabled);
-                    return true;
-                }
-                final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
+                if (userId == mCurrentUserId) {
+                    setInputMethodEnabledLocked(imeId, enabled);
+                    return true;
+                }
                 if (enabled) {
                     final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
                     final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
@@ -5858,8 +5840,9 @@
         final Printer p = new PrintWriterPrinter(pw);
 
         synchronized (ImfLock.class) {
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
             p.println("Current Input Method Manager state:");
-            final List<InputMethodInfo> methodList = mSettings.getMethodList();
+            final List<InputMethodInfo> methodList = settings.getMethodList();
             int numImes = methodList.size();
             p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
             for (int i = 0; i < numImes; i++) {
@@ -5883,6 +5866,7 @@
                 p.println("    curSession=" + c.mCurSession);
             };
             mClientController.forAllClients(clientControllerDump);
+            p.println("  mCurrentUserId=" + mCurrentUserId);
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
@@ -5908,8 +5892,6 @@
                     ? Arrays.toString(mStylusIds.toArray()) : ""));
             p.println("  mSwitchingController:");
             mSwitchingController.dump(p);
-            p.println("  mSettings:");
-            mSettings.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
@@ -6164,7 +6146,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                    mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                    mCurrentUserId, shellCommand.getErrPrintWriter());
             try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
                 for (int userId : userIds) {
                     final List<InputMethodInfo> methods = all
@@ -6209,7 +6191,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6268,14 +6250,14 @@
             PrintWriter error) {
         boolean failedToEnableUnknownIme = false;
         boolean previouslyEnabled = false;
-        if (userId == mSettings.getUserId()) {
-            if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (userId == mCurrentUserId) {
+            if (enabled && !settings.getMethodMap().containsKey(imeId)) {
                 failedToEnableUnknownIme = true;
             } else {
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6330,7 +6312,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6370,7 +6352,7 @@
         synchronized (ImfLock.class) {
             try (PrintWriter out = shellCommand.getOutPrintWriter()) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6382,15 +6364,16 @@
                     }
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
-                    if (userId == mSettings.getUserId()) {
+                    final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+                    if (userId == mCurrentUserId) {
                         hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
-                        var toDisable = mSettings.getEnabledInputMethodList();
+                        var toDisable = settings.getEnabledInputMethodList();
                         var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
-                                mContext, mSettings.getMethodList());
+                                mContext, settings.getMethodList());
                         toDisable.removeAll(defaultEnabled);
                         for (InputMethodInfo info : toDisable) {
                             setInputMethodEnabledLocked(info.getId(), false);
@@ -6404,16 +6387,11 @@
                         }
                         updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
                         InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
-                                getPackageManagerForUser(mContext, mSettings.getUserId()),
-                                mSettings.getEnabledInputMethodList());
-                        nextIme = mSettings.getSelectedInputMethod();
-                        nextEnabledImes = mSettings.getEnabledInputMethodList();
+                                getPackageManagerForUser(mContext, settings.getUserId()),
+                                settings.getEnabledInputMethodList());
+                        nextIme = settings.getSelectedInputMethod();
+                        nextEnabledImes = settings.getEnabledInputMethodList();
                     } else {
-                        final AdditionalSubtypeMap additionalSubtypeMap =
-                                AdditionalSubtypeMapRepository.get(userId);
-                        final InputMethodSettings settings = queryInputMethodServicesInternal(
-                                mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
-
                         nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
                                 settings.getMethodList());
                         nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodSettingsRepository() {
+    }
+
+    @NonNull
+    @GuardedBy("ImfLock.class")
+    static InputMethodSettings get(@UserIdInt int userId) {
+        final InputMethodSettings obj = sPerUserMap.get(userId);
+        if (obj != null) {
+            return obj;
+        }
+        return InputMethodSettings.createEmptyMap(userId);
+    }
+
+    @GuardedBy("ImfLock.class")
+    static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+        sPerUserMap.put(userId, obj);
+    }
+
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        handler.post(() -> {
+            userManagerInternal.addUserLifecycleListener(
+                    new UserManagerInternal.UserLifecycleListener() {
+                        @Override
+                        public void onUserRemoved(UserInfo user) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    sPerUserMap.remove(userId);
+                                }
+                            });
+                        }
+                    });
+            synchronized (ImfLock.class) {
+                for (int userId : userManagerInternal.getUserIds()) {
+                    final InputMethodSettings settings =
+                            InputMethodManagerService.queryInputMethodServicesInternal(
+                                    context,
+                                    userId,
+                                    AdditionalSubtypeMapRepository.get(userId),
+                                    DirectBootAwareness.AUTO);
+                    sPerUserMap.put(userId, settings);
+                }
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 194ab04..3d68555 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1458,14 +1458,10 @@
         @Override
         public IBinder getBinderForSetQueue() throws RemoteException {
             return new ParcelableListBinder<QueueItem>(
+                    QueueItem.class,
                     (list) -> {
-                        // Checking list items are instanceof QueueItem to validate against
-                        // malicious apps calling it directly via reflection with non compilable
-                        // items. See b/317048338 for more details
-                        List<QueueItem> sanitizedQueue =
-                                list.stream().filter(it -> it instanceof QueueItem).toList();
                         synchronized (mLock) {
-                            mQueue = sanitizedQueue;
+                            mQueue = list;
                         }
                         mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
                     });
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index bd73cb6..1938642 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -23,9 +23,15 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.LoggingOnly;
 import android.content.Context;
 import android.media.AudioAttributes;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Slog;
+import com.android.internal.compat.IPlatformCompat;
 
 /**
  * Stores the latest notification channel information for this notification
@@ -34,14 +40,26 @@
     private static final String TAG = "ChannelExtractor";
     private static final boolean DBG = false;
 
+    /**
+     * Corrects audio attributes for notifications based on characteristics of the notifications.
+     */
+    @ChangeId
+    @LoggingOnly
+    static final long RESTRICT_AUDIO_ATTRIBUTES = 331793339L;
+
     private RankingConfig mConfig;
     private Context mContext;
+    private IPlatformCompat mPlatformCompat;
 
     public void initialize(Context ctx, NotificationUsageStats usageStats) {
         mContext = ctx;
         if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
     }
 
+    public void setCompatChangeLogger(IPlatformCompat platformCompat) {
+        mPlatformCompat = platformCompat;
+    }
+
     public RankingReconsideration process(NotificationRecord record) {
         if (record == null || record.getNotification() == null) {
             if (DBG) Slog.d(TAG, "skipping empty notification");
@@ -80,6 +98,7 @@
             }
 
             if (updateAttributes) {
+                reportAudioAttributesChanged(record.getUid());
                 NotificationChannel clone = record.getChannel().copy();
                 clone.setSound(clone.getSound(), new AudioAttributes.Builder(attributes)
                         .setUsage(USAGE_NOTIFICATION)
@@ -91,6 +110,17 @@
         return null;
     }
 
+    private void reportAudioAttributesChanged(int uid) {
+        final long id = Binder.clearCallingIdentity();
+        try {
+            mPlatformCompat.reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, uid);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unexpected exception while reporting to changecompat", e);
+        } finally {
+            Binder.restoreCallingIdentity(id);
+        }
+    }
+
     @Override
     public void setConfig(RankingConfig config) {
         mConfig = config;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
index 2cc63eb..a3a91e2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
@@ -25,4 +25,6 @@
 interface NotificationManagerPrivate {
     @Nullable
     NotificationRecord getNotificationByKey(String key);
+
+    void timeoutNotification(String key);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b14242e..b48cad2 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -139,7 +139,6 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
-
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -221,6 +220,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -238,6 +238,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.DeviceIdleManager;
 import android.os.Environment;
 import android.os.Handler;
@@ -305,7 +306,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.RemoteViews;
 import android.widget.Toast;
-
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -357,9 +357,7 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.BackgroundActivityStartCallback;
 import com.android.server.wm.WindowManagerInternal;
-
 import libcore.io.IoUtils;
-
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParserException;
@@ -521,7 +519,7 @@
     /**
      * Apps that post custom toasts in the background will have those blocked. Apps can
      * still post toasts created with
-     * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while
+     * {@link Toast#makeText(Context, CharSequence, int)} and its variants while
      * in the background.
      */
     @ChangeId
@@ -556,7 +554,7 @@
     /**
      * Rate limit showing toasts, on a per package basis.
      *
-     * It limits the number of {@link android.widget.Toast#show()} calls to prevent overburdening
+     * It limits the number of {@link Toast#show()} calls to prevent overburdening
      * the user with too many toasts in a limited time. Any attempt to show more toasts than allowed
      * in a certain time frame will result in the toast being discarded.
      */
@@ -580,9 +578,9 @@
     static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L;
 
     /**
-     * App calls to {@link android.app.NotificationManager#setInterruptionFilter} and
-     * {@link android.app.NotificationManager#setNotificationPolicy} manage DND through the
-     * creation and activation of an implicit {@link android.app.AutomaticZenRule}.
+     * App calls to {@link NotificationManager#setInterruptionFilter} and
+     * {@link NotificationManager#setNotificationPolicy} manage DND through the
+     * creation and activation of an implicit {@link AutomaticZenRule}.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -624,6 +622,8 @@
     private PowerManager mPowerManager;
     private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
 
+    private LockPatternUtils mLockUtils;
+
     final IBinder mForegroundToken = new Binder();
     @VisibleForTesting
     WorkerHandler mHandler;
@@ -676,6 +676,10 @@
 
     private static final int DB_VERSION = 1;
 
+
+    private static final String ADSERVICES_MODULE_PKG_NAME =
+            "com.android.adservices";
+
     private static final String TAG_NOTIFICATION_POLICY = "notification-policy";
     private static final String ATTR_VERSION = "version";
 
@@ -709,6 +713,7 @@
 
     private NotificationHistoryManager mHistoryManager;
     protected SnoozeHelper mSnoozeHelper;
+    private TimeToLiveHelper mTtlHelper;
     private GroupHelper mGroupHelper;
     private int mAutoGroupAtCount;
     private boolean mIsTelevision;
@@ -734,6 +739,10 @@
     // Broadcast intent receiver for notification permissions review-related intents
     private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
 
+    private AppOpsManager.OnOpChangedListener mAppOpsListener;
+
+    private ModuleInfo mAdservicesModuleInfo;
+
     static class Archive {
         final SparseArray<Boolean> mEnabled;
         final int mBufferSize;
@@ -779,7 +788,7 @@
 
         public StatusBarNotification[] getArray(UserManager um, int count, boolean includeSnoozed) {
             ArrayList<Integer> currentUsers = new ArrayList<>();
-            currentUsers.add(UserHandle.USER_ALL);
+            currentUsers.add(USER_ALL);
             Binder.withCleanCallingIdentity(() -> {
                 for (int user : um.getProfileIds(ActivityManager.getCurrentUser(), false)) {
                     currentUsers.add(user);
@@ -902,14 +911,14 @@
 
     @VisibleForTesting
     boolean isDNDMigrationDone(int userId) {
-        return Settings.Secure.getIntForUser(getContext().getContentResolver(),
-                Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+        return Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
     }
 
     @VisibleForTesting
     void setDNDMigrationDone(int userId) {
-        Settings.Secure.putIntForUser(getContext().getContentResolver(),
-                Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
+        Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.DND_CONFIGS_MIGRATED, 1, userId);
     }
 
     protected void migrateDefaultNAS() {
@@ -936,15 +945,15 @@
     @VisibleForTesting
     void setNASMigrationDone(int baseUserId) {
         for (int profileId : mUm.getProfileIds(baseUserId, false)) {
-            Settings.Secure.putIntForUser(getContext().getContentResolver(),
-                    Settings.Secure.NAS_SETTINGS_UPDATED, 1, profileId);
+            Secure.putIntForUser(getContext().getContentResolver(),
+                    Secure.NAS_SETTINGS_UPDATED, 1, profileId);
         }
     }
 
     @VisibleForTesting
     boolean isNASMigrationDone(int userId) {
-        return (Settings.Secure.getIntForUser(getContext().getContentResolver(),
-                Settings.Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
+        return (Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
     }
 
     boolean isProfileUser(UserInfo userInfo) {
@@ -1097,7 +1106,7 @@
                 mSnoozeHelper.readXml(parser, System.currentTimeMillis());
             }
             if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
-                if (forRestore && userId != UserHandle.USER_SYSTEM) {
+                if (forRestore && userId != USER_SYSTEM) {
                     continue;
                 }
                 mLockScreenAllowSecureNotifications = parser.getAttributeBoolean(null,
@@ -1141,7 +1150,7 @@
             InputStream infile = null;
             try {
                 infile = mPolicyFile.openRead();
-                readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+                readPolicyXml(infile, false /*forRestore*/, USER_ALL);
 
                 // We re-load the default dnd packages to allow the newly added and denined.
                 final boolean isWatch = mPackageManagerClient.hasSystemFeature(
@@ -1191,7 +1200,7 @@
                 }
 
                 try {
-                    writePolicyXml(stream, false /*forBackup*/, UserHandle.USER_ALL);
+                    writePolicyXml(stream, false /*forBackup*/, USER_ALL);
                     mPolicyFile.finishWrite(stream);
                 } catch (IOException e) {
                     Slog.w(TAG, "Failed to save policy file, restoring backup", e);
@@ -1220,7 +1229,7 @@
         mAssistants.writeXml(out, forBackup, userId);
         mSnoozeHelper.writeXml(out);
         mConditionProviders.writeXml(out, forBackup, userId);
-        if (!forBackup || userId == UserHandle.USER_SYSTEM) {
+        if (!forBackup || userId == USER_SYSTEM) {
             writeSecureNotificationsPolicy(out);
         }
         out.endTag(null, TAG_NOTIFICATION_POLICY);
@@ -1273,7 +1282,7 @@
 
                 StatusBarNotification sbn = r.getSbn();
                 cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
-                        sbn.getId(), Notification.FLAG_AUTO_CANCEL,
+                        sbn.getId(), FLAG_AUTO_CANCEL,
                         FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
                         false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
                 nv.recycle();
@@ -1761,9 +1770,50 @@
 
     };
 
-    NotificationManagerPrivate mNotificationManagerPrivate = key -> {
-        synchronized (mNotificationLock) {
-            return mNotificationsByKey.get(key);
+    NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
+        @Nullable
+        @Override
+        public NotificationRecord getNotificationByKey(String key) {
+            synchronized (mNotificationLock) {
+                return mNotificationsByKey.get(key);
+            }
+        }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+        public void timeoutNotification(String key) {
+            boolean foundNotification = false;
+            int uid = 0;
+            int pid = 0;
+            String packageName = null;
+            String tag = null;
+            int id = 0;
+            int userId = 0;
+
+            synchronized (mNotificationLock) {
+                NotificationRecord record = findNotificationByKeyLocked(key);
+                if (record != null) {
+                    foundNotification = true;
+                    uid = record.getUid();
+                    pid = record.getSbn().getInitialPid();
+                    packageName = record.getSbn().getPackageName();
+                    tag = record.getSbn().getTag();
+                    id = record.getSbn().getId();
+                    userId = record.getUserId();
+                }
+            }
+            if (foundNotification) {
+                if (lifetimeExtensionRefactor()) {
+                    cancelNotification(uid, pid, packageName, tag, id, 0,
+                            FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
+                                    | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
+                            true, userId, REASON_TIMEOUT, null);
+                } else {
+                    cancelNotification(uid, pid, packageName, tag, id, 0,
+                            FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+                            true, userId, REASON_TIMEOUT, null);
+                }
+            }
         }
     };
 
@@ -1893,7 +1943,7 @@
                     || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
                     || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
                 int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_ALL);
+                        USER_ALL);
                 String pkgList[] = null;
                 int uidList[] = null;
                 boolean removingPackage = queryRemove &&
@@ -1942,7 +1992,7 @@
                         try {
                             final int enabled = mPackageManager.getApplicationEnabledSetting(
                                     pkgName,
-                                    changeUserId != UserHandle.USER_ALL ? changeUserId :
+                                    changeUserId != USER_ALL ? changeUserId :
                                             USER_SYSTEM);
                             if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                                     || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -2055,22 +2105,22 @@
 
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
-                = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
+                = Secure.getUriFor(Secure.NOTIFICATION_BADGING);
         private final Uri NOTIFICATION_BUBBLES_URI
-                = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+                = Secure.getUriFor(Secure.NOTIFICATION_BUBBLES);
         private final Uri NOTIFICATION_RATE_LIMIT_URI
                 = Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
         private final Uri NOTIFICATION_HISTORY_ENABLED
-                = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+                = Secure.getUriFor(Secure.NOTIFICATION_HISTORY_ENABLED);
         private final Uri NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI
                 = Settings.Global.getUriFor(Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS);
         private final Uri LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
-                = Settings.Secure.getUriFor(
-                        Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+                = Secure.getUriFor(
+                        Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS
-                = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+                = Secure.getUriFor(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
         private final Uri SHOW_NOTIFICATION_SNOOZE
-                = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE);
+                = Secure.getUriFor(Secure.SHOW_NOTIFICATION_SNOOZE);
 
         SettingsObserver(Handler handler) {
             super(handler);
@@ -2079,27 +2129,31 @@
         void observe() {
             ContentResolver resolver = getContext().getContentResolver();
             resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_HISTORY_ENABLED,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
 
             resolver.registerContentObserver(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
             resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
 
             resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
 
             update(null);
         }
 
+        void destroy() {
+            getContext().getContentResolver().unregisterContentObserver(this);
+        }
+
         @Override public void onChange(boolean selfChange, Uri uri, int userId) {
             update(uri);
         }
@@ -2131,7 +2185,7 @@
                 mPreferencesHelper.updateLockScreenShowNotifications();
             }
             if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) {
-                final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver,
+                final boolean snoozeEnabled = Secure.getIntForUser(resolver,
                         Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT)
                         != 0;
                 if (!snoozeEnabled) {
@@ -2144,8 +2198,8 @@
             ContentResolver resolver = getContext().getContentResolver();
             if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
                 mArchive.updateHistoryEnabled(userId,
-                        Settings.Secure.getIntForUser(resolver,
-                                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+                        Secure.getIntForUser(resolver,
+                                Secure.NOTIFICATION_HISTORY_ENABLED, 0,
                                 userId) == 1);
                 // note: this setting is also handled in NotificationHistoryManager
             }
@@ -2229,6 +2283,11 @@
     }
 
     @VisibleForTesting
+    void setLockPatternUtils(LockPatternUtils lockUtils) {
+        mLockUtils = lockUtils;
+    }
+
+    @VisibleForTesting
     ShortcutHelper getShortcutHelper() {
         return mShortcutHelper;
     }
@@ -2400,7 +2459,7 @@
                     getContext().sendBroadcastAsUser(
                             new Intent(ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
-                            UserHandle.ALL, permission.MANAGE_NOTIFICATIONS);
+                            UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
                     synchronized (mNotificationLock) {
                         updateInterruptionFilterLocked();
                     }
@@ -2456,15 +2515,14 @@
                 mAppOps,
                 mUserProfiles,
                 mShowReviewPermissionsNotification);
-        mRankingHelper = new RankingHelper(getContext(),
-                mRankingHandler,
-                mPreferencesHelper,
-                mZenModeHelper,
-                mUsageStats,
-                extractorNames);
+        mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
+                mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
         mSnoozeHelper = snoozeHelper;
         mGroupHelper = groupHelper;
         mHistoryManager = historyManager;
+        if (Flags.allNotifsNeedTtl()) {
+            mTtlHelper = new TimeToLiveHelper(mNotificationManagerPrivate, getContext());
+        }
 
         // This is a ManagedServices object that keeps track of the listeners.
         mListeners = notificationListeners;
@@ -2551,10 +2609,12 @@
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
                 null);
 
-        IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
-        timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
-        getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
+        if (!Flags.allNotifsNeedTtl()) {
+            IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+            timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+            getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
+        }
 
         IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
         getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
@@ -2567,15 +2627,16 @@
                 ReviewNotificationPermissionsReceiver.getFilter(),
                 Context.RECEIVER_NOT_EXPORTED);
 
-        mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
-                new AppOpsManager.OnOpChangedInternalListener() {
-                    @Override
-                    public void onOpChanged(@NonNull String op, @NonNull String packageName,
-                            int userId) {
-                        mHandler.post(
-                                () -> handleNotificationPermissionChange(packageName, userId));
-                    }
-                });
+        mAppOpsListener = new AppOpsManager.OnOpChangedInternalListener() {
+            @Override
+            public void onOpChanged(@NonNull String op, @NonNull String packageName,
+                    int userId) {
+                mHandler.post(
+                        () -> handleNotificationPermissionChange(packageName, userId));
+            }
+        };
+
+        mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsListener);
     }
 
     /**
@@ -2584,10 +2645,26 @@
     public void onDestroy() {
         getContext().unregisterReceiver(mIntentReceiver);
         getContext().unregisterReceiver(mPackageIntentReceiver);
-        getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+        if (Flags.allNotifsNeedTtl()) {
+            mTtlHelper.destroy();
+        } else {
+            getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+        }
         getContext().unregisterReceiver(mRestoreReceiver);
         getContext().unregisterReceiver(mLocaleChangeReceiver);
 
+        mSettingsObserver.destroy();
+        mRoleObserver.destroy();
+        if (mShortcutHelper != null) {
+            mShortcutHelper.destroy();
+        }
+        mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+        mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+        mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+        mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+        mAppOps.stopWatchingMode(mAppOpsListener);
+        mAlarmManager.cancelAll();
+
         if (mDeviceConfigChangedListener != null) {
             DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
         }
@@ -2842,7 +2919,10 @@
                 bubbsExtractor.setShortcutHelper(mShortcutHelper);
             }
             registerNotificationPreferencesPullers();
-            new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
+            if (mLockUtils == null) {
+                mLockUtils = new LockPatternUtils(getContext());
+            }
+            mLockUtils.registerStrongAuthTracker(mStrongAuthTracker);
             mAttentionHelper.onSystemReady();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
@@ -2861,6 +2941,15 @@
                 mZenModeHelper.setDeviceEffectsApplier(
                         new DefaultDeviceEffectsApplier(getContext()));
             }
+            List<ModuleInfo> moduleInfoList =
+            mPackageManagerClient.getInstalledModules(
+                PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
+            // Cache adservices module info
+            for (ModuleInfo mi : moduleInfoList) {
+                if (Objects.equals(mi.getApexModuleName(), ADSERVICES_MODULE_PKG_NAME)) {
+                    mAdservicesModuleInfo = mi;
+                }
+            }
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
         } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
@@ -2952,7 +3041,7 @@
 
     void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
             boolean fromListener) {
-        if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+        if (channel.getImportance() == IMPORTANCE_NONE) {
             // cancel
             cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
                     UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
@@ -3217,14 +3306,14 @@
                         | SUPPRESSED_EFFECT_SCREEN_OFF);
 
                 // set the deprecated effects according to the new more specific effects
-                if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) != 0) {
+                if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) != 0) {
                     newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_ON;
                 }
-                if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) != 0
+                if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) != 0
                         && (newSuppressedVisualEffects
-                        & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
+                        & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
                         && (newSuppressedVisualEffects
-                        & Policy.SUPPRESSED_EFFECT_AMBIENT) != 0) {
+                        & SUPPRESSED_EFFECT_AMBIENT) != 0) {
                     newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_OFF;
                 }
             } else {
@@ -3292,7 +3381,7 @@
         if (n.extras != null) {
             title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
             if (title == null) {
-                title = n.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+                title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
             }
         }
         return title == null ? getContext().getResources().getString(
@@ -3311,9 +3400,9 @@
 
             if (nb.getStyle() instanceof Notification.BigTextStyle) {
                 text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
-            } else if (nb.getStyle() instanceof Notification.MessagingStyle) {
-                Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle();
-                final List<Notification.MessagingStyle.Message> messages = ms.getMessages();
+            } else if (nb.getStyle() instanceof MessagingStyle) {
+                MessagingStyle ms = (MessagingStyle) nb.getStyle();
+                final List<MessagingStyle.Message> messages = ms.getMessages();
                 if (messages != null && messages.size() > 0) {
                     text = messages.get(messages.size() - 1).getText();
                 }
@@ -3364,7 +3453,7 @@
     }
 
     private int getRealUserId(int userId) {
-        return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
+        return userId == USER_ALL ? USER_SYSTEM : userId;
     }
 
     private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
@@ -4096,8 +4185,8 @@
         public void createConversationNotificationChannelForPackage(String pkg, int uid,
                 NotificationChannel parentChannel, String conversationId) {
             enforceSystemOrSystemUI("only system can call this");
-            Preconditions.checkNotNull(parentChannel);
-            Preconditions.checkNotNull(conversationId);
+            checkNotNull(parentChannel);
+            checkNotNull(conversationId);
             String parentId = parentChannel.getId();
             NotificationChannel conversationChannel = parentChannel;
             conversationChannel.setId(String.format(
@@ -4542,7 +4631,7 @@
             int uid = Binder.getCallingUid();
 
             ArrayList<Integer> currentUsers = new ArrayList<>();
-            currentUsers.add(UserHandle.USER_ALL);
+            currentUsers.add(USER_ALL);
             Binder.withCleanCallingIdentity(() -> {
                 for (int user : mUm.getProfileIds(ActivityManager.getCurrentUser(), false)) {
                     currentUsers.add(user);
@@ -4798,7 +4887,7 @@
          * Register a listener binder directly with the notification manager.
          *
          * Only works with system callers. Apps should extend
-         * {@link android.service.notification.NotificationListenerService}.
+         * {@link NotificationListenerService}.
          */
         @Override
         public void registerListener(final INotificationListener listener,
@@ -4855,7 +4944,7 @@
                             NotificationRecord r = mNotificationsByKey.get(keys[i]);
                             if (r == null) continue;
                             final int userId = r.getSbn().getUserId();
-                            if (userId != info.userid && userId != UserHandle.USER_ALL &&
+                            if (userId != info.userid && userId != USER_ALL &&
                                     !mUserProfiles.isCurrentProfile(userId)) {
                                 continue;
                             }
@@ -4972,7 +5061,7 @@
                         NotificationRecord r = mNotificationsByKey.get(keys[i]);
                         if (r == null) continue;
                         final int userId = r.getSbn().getUserId();
-                        if (userId != info.userid && userId != UserHandle.USER_ALL
+                        if (userId != info.userid && userId != USER_ALL
                                 && !mUserProfiles.isCurrentProfile(userId)) {
                             continue;
                         }
@@ -5639,7 +5728,7 @@
         }
 
         private void enforcePolicyAccess(int uid, String method) {
-            if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+            if (PERMISSION_GRANTED == getContext().checkCallingPermission(
                     android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
                 return;
             }
@@ -5670,7 +5759,7 @@
         }
 
         private void enforcePolicyAccess(String pkg, String method) {
-            if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+            if (PERMISSION_GRANTED == getContext().checkCallingPermission(
                     android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
                 return;
             }
@@ -5691,7 +5780,7 @@
             try {
                 uid = getContext().getPackageManager().getPackageUidAsUser(pkg,
                         UserHandle.getCallingUserId());
-                if (PackageManager.PERMISSION_GRANTED == checkComponentPermission(
+                if (PERMISSION_GRANTED == checkComponentPermission(
                         android.Manifest.permission.MANAGE_NOTIFICATIONS, uid,
                         -1, true)) {
                     return true;
@@ -5886,7 +5975,7 @@
 
         /**
          * Sets the notification policy.  Apps that target API levels below
-         * {@link android.os.Build.VERSION_CODES#P} cannot change user-designated values to
+         * {@link Build.VERSION_CODES#P} cannot change user-designated values to
          * allow or disallow {@link Policy#PRIORITY_CATEGORY_ALARMS},
          * {@link Policy#PRIORITY_CATEGORY_SYSTEM} and
          * {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
@@ -6254,7 +6343,7 @@
 
         @Override
         public void setPrivateNotificationsAllowed(boolean allow) {
-            if (PackageManager.PERMISSION_GRANTED
+            if (PERMISSION_GRANTED
                     != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
                 throw new SecurityException(
                         "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6275,7 +6364,7 @@
 
         @Override
         public boolean getPrivateNotificationsAllowed() {
-            if (PackageManager.PERMISSION_GRANTED
+            if (PERMISSION_GRANTED
                     != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
                 throw new SecurityException(
                         "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6563,9 +6652,9 @@
                 // Add summary
                 final ApplicationInfo appInfo =
                         adjustedSbn.getNotification().extras.getParcelable(
-                                Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
+                                EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
                 final Bundle extras = new Bundle();
-                extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
+                extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, appInfo);
                 final String channelId = notificationRecord.getChannel().getId();
 
                 final Notification summaryNotification =
@@ -6859,6 +6948,11 @@
             if (!zenOnly) {
                 pw.println("\n  Usage Stats:");
                 mUsageStats.dump(pw, "    ", filter);
+
+                if (Flags.allNotifsNeedTtl()) {
+                    pw.println("\n  TimeToLive alarms:");
+                    mTtlHelper.dump(pw, "    ");
+                }
             }
         }
     }
@@ -7455,7 +7549,7 @@
             throws NameNotFoundException, RemoteException {
         final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                 pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
+                (userId == USER_ALL) ? USER_SYSTEM : userId);
         Notification.addFieldsFromContext(ai, notification);
 
         if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
@@ -7570,7 +7664,7 @@
             // Enforce NO_CLEAR flag on MediaStyle notification for apps with targetSdk >= V.
             if (CompatChanges.isChangeEnabled(ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION,
                     notificationUid)) {
-                notification.flags |= Notification.FLAG_NO_CLEAR;
+                notification.flags |= FLAG_NO_CLEAR;
             }
         }
 
@@ -7605,13 +7699,27 @@
     private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
         return notification.isMediaNotification() || isEnterpriseExempted(ai)
                 || notification.isStyle(Notification.CallStyle.class)
-                || isDefaultSearchSelectorPackage(ai.packageName);
+                || isDefaultSearchSelectorPackage(ai.packageName)
+                || isDefaultAdservicesPackage(ai.packageName);
     }
 
     private boolean isDefaultSearchSelectorPackage(String pkg) {
         return Objects.equals(mDefaultSearchSelectorPkg, pkg);
     }
 
+    private boolean isDefaultAdservicesPackage(String pkg) {
+        if (mAdservicesModuleInfo == null) {
+            return false;
+        }
+        // Handles the special package structure for mainline modules
+        for (String apkName : mAdservicesModuleInfo.getApkInApexPackageNames()) {
+            if (Objects.equals(apkName, pkg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean isEnterpriseExempted(ApplicationInfo ai) {
         // Check if the app is an organization admin app
         // TODO(b/234609037): Replace with new DPM APIs to check if organization admin
@@ -7622,7 +7730,7 @@
         // Check if an app has been given system exemption
         return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow(
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
-                ai.packageName) == AppOpsManager.MODE_ALLOWED;
+                ai.packageName) == MODE_ALLOWED;
     }
 
     private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
@@ -7727,7 +7835,7 @@
                             // Enqueue will trigger resort & flag is updated that way.
                             r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
                             mHandler.post(
-                                    new NotificationManagerService.EnqueueNotificationRunnable(
+                                    new EnqueueNotificationRunnable(
                                             r.getUser().getIdentifier(), r, isAppForeground,
                                             mPostNotificationTrackerFactory.newTracker(null)));
                         }
@@ -7749,7 +7857,7 @@
 
     @VisibleForTesting
     int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
-        if (userId == UserHandle.USER_ALL) {
+        if (userId == USER_ALL) {
             userId = USER_SYSTEM;
         }
         // posted from app A on behalf of app A
@@ -8008,7 +8116,7 @@
         final String pkg = r.getSbn().getPackageName();
         final int callingUid = r.getSbn().getUid();
         return mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
-                || r.getImportance() == NotificationManager.IMPORTANCE_NONE;
+                || r.getImportance() == IMPORTANCE_NONE;
     }
 
     protected class SnoozeNotificationRunnable implements Runnable {
@@ -8347,7 +8455,11 @@
                 }
 
                 mEnqueuedNotifications.add(r);
-                scheduleTimeoutLocked(r);
+                if (Flags.allNotifsNeedTtl()) {
+                    mTtlHelper.scheduleTimeoutLocked(r, SystemClock.elapsedRealtime());
+                } else {
+                    scheduleTimeoutLocked(r);
+                }
 
                 final StatusBarNotification n = r.getSbn();
                 if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
@@ -8567,7 +8679,7 @@
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
                         if (old != null && !old.isCanceled) {
                             mListeners.notifyRemovedLocked(r,
-                                    NotificationListenerService.REASON_ERROR, r.getStats());
+                                    REASON_ERROR, r.getStats());
                             mHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
@@ -8935,7 +9047,7 @@
         try {
             isExemptFromRateLimiting = mPackageManager.checkPermission(
                     android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
-                    == PackageManager.PERMISSION_GRANTED;
+                    == PERMISSION_GRANTED;
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to connect with package manager");
         }
@@ -9460,7 +9572,11 @@
             int rank, int count, boolean wasPosted, String listenerName,
             @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         final String canceledKey = r.getKey();
-        cancelScheduledTimeoutLocked(r);
+        if (Flags.allNotifsNeedTtl()) {
+            mTtlHelper.cancelScheduledTimeoutLocked(r);
+        } else {
+            cancelScheduledTimeoutLocked(r);
+        }
 
         // Record caller.
         recordCallerLocked(r);
@@ -9637,7 +9753,7 @@
                         // Uri, not when removing an individual listener.
                         revokeUriPermission(permissionOwner, uri,
                                 UserHandle.getUserId(oldRecord.getUid()),
-                                null, UserHandle.USER_ALL);
+                                null, USER_ALL);
                     }
                 }
             }
@@ -9735,9 +9851,9 @@
         } else {
             return
                 // looking for USER_ALL notifications? match everything
-                userId == UserHandle.USER_ALL
+                userId == USER_ALL
                         // a notification sent to USER_ALL matches any query
-                        || r.getUserId() == UserHandle.USER_ALL
+                        || r.getUserId() == USER_ALL
                         // an exact user match
                         || r.getUserId() == userId;
         }
@@ -9816,7 +9932,7 @@
                 continue;
             }
             // Don't remove notifications to all, if there's no package name specified
-            if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+            if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == USER_ALL) {
                 continue;
             }
             if (!flagChecker.apply(r.getFlags())) {
@@ -10314,7 +10430,7 @@
             return false;
         }
 
-        if (userId == UserHandle.USER_ALL) {
+        if (userId == USER_ALL) {
             userId = USER_SYSTEM;
         }
 
@@ -10537,7 +10653,7 @@
         if (requiredPermission != null) {
             try {
                 if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
-                        != PackageManager.PERMISSION_GRANTED) {
+                        != PERMISSION_GRANTED) {
                     canUseManagedServices = false;
                 }
             } catch (RemoteException e) {
@@ -10663,7 +10779,7 @@
             c.caption = "notification assistant";
             c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
             c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
-            c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
+            c.secureSettingName = Secure.ENABLED_NOTIFICATION_ASSISTANT;
             c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
             c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
             c.clientLabel = R.string.notification_ranker_binding_label;
@@ -11259,7 +11375,7 @@
             c.caption = "notification listener";
             c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
             c.xmlTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
-            c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+            c.secureSettingName = Secure.ENABLED_NOTIFICATION_LISTENERS;
             c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
             c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
             c.clientLabel = R.string.notification_listener_binding_label;
@@ -11667,7 +11783,7 @@
                         // Managed Services.
                         if (info.isSystemUi() && old != null && old.getNotification() != null
                                 && (old.getNotification().flags
-                                & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+                                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
                             final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
                             listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
                             break;
@@ -11696,8 +11812,8 @@
                         continue;
                     }
                     // Grant access before listener is notified
-                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
-                            ? UserHandle.USER_SYSTEM : info.userid;
+                    final int targetUserId = (info.userid == USER_ALL)
+                            ? USER_SYSTEM : info.userid;
                     updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
 
                     mPackageManagerInternal.grantImplicitAccess(
@@ -11846,8 +11962,8 @@
                         continue;
                     }
                     // Grant or revoke access synchronously
-                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
-                            ? UserHandle.USER_SYSTEM : info.userid;
+                    final int targetUserId = (info.userid == USER_ALL)
+                            ? USER_SYSTEM : info.userid;
                     if (grant) {
                         // Grant permissions by passing arguments as if the notification is new.
                         updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
@@ -11919,7 +12035,7 @@
             }
 
             // Revoke access after all listeners have been updated
-            mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
+            mHandler.post(() -> updateUriPermissions(null, r, null, USER_SYSTEM));
         }
 
         /**
@@ -12079,7 +12195,7 @@
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
                 listener.onNotificationPosted(sbnHolder, rankingUpdate);
-            } catch (android.os.DeadObjectException ex) {
+            } catch (DeadObjectException ex) {
                 Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
@@ -12102,7 +12218,7 @@
                     reason = REASON_LISTENER_CANCEL;
                 }
                 listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
-            } catch (android.os.DeadObjectException ex) {
+            } catch (DeadObjectException ex) {
                 Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
@@ -12114,7 +12230,7 @@
             final INotificationListener listener = (INotificationListener) info.service;
             try {
                 listener.onNotificationRankingUpdate(rankingUpdate);
-            } catch (android.os.DeadObjectException ex) {
+            } catch (DeadObjectException ex) {
                 Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
@@ -12333,6 +12449,10 @@
             mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
         }
 
+        void destroy() {
+            mRm.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.ALL);
+        }
+
         @VisibleForTesting
         public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) {
             return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
@@ -12621,7 +12741,7 @@
                 .setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .setStyle(new Notification.BigTextStyle())
-                .setFlag(Notification.FLAG_NO_CLEAR, true)
+                .setFlag(FLAG_NO_CLEAR, true)
                 .setAutoCancel(true)
                 .addAction(remindMe)
                 .addAction(dismiss)
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 97d2620..c69bead 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -79,6 +79,7 @@
 
 import java.io.PrintWriter;
 import java.lang.reflect.Array;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -600,8 +601,7 @@
         pw.println(prefix + "headsUpContentView="
                 + formatRemoteViews(notification.headsUpContentView));
         pw.println(prefix + String.format("color=0x%08x", notification.color));
-        pw.println(prefix + "timeout="
-                + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
+        pw.println(prefix + "timeout=" + Duration.ofMillis(notification.getTimeoutAfter()));
         if (notification.actions != null && notification.actions.length > 0) {
             pw.println(prefix + "actions={");
             final int N = notification.actions.length;
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index 24c1d59..f0358d1 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import android.content.Context;
+import com.android.internal.compat.IPlatformCompat;
 
 /**
  * Extracts signals that will be useful to the {@link NotificationComparator} and caches them
@@ -52,4 +53,6 @@
      *               DND.
      */
     void setZenHelper(ZenModeHelper helper);
+
+    default void setCompatChangeLogger(IPlatformCompat platformCompat){};
 }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 50ca984..461bd9c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1387,8 +1387,7 @@
     public void updateFixedImportance(List<UserInfo> users) {
         for (UserInfo user : users) {
             List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
-                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY),
-                    user.getUserHandle().getIdentifier());
+                    0, user.getUserHandle().getIdentifier());
             for (PackageInfo pi : packages) {
                 boolean fixed = mPermissionHelper.isPermissionFixed(
                         pi.packageName, user.getUserHandle().getIdentifier());
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 68e0eaa..7756801 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.notification;
 
+import static android.app.Flags.restrictAudioAttributesAlarm;
+import static android.app.Flags.restrictAudioAttributesCall;
+import static android.app.Flags.restrictAudioAttributesMedia;
 import static android.app.Flags.sortSectionByTime;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.text.TextUtils.formatSimple;
@@ -27,6 +30,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.compat.IPlatformCompat;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.keepanno.annotations.UsesReflection;
@@ -56,7 +60,8 @@
                         methodName = "<init>")
             })
     public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
-            ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
+            ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
+            IPlatformCompat platformCompat) {
         mContext = context;
         mRankingHandler = rankingHandler;
         if (sortSectionByTime()) {
@@ -75,6 +80,10 @@
                 extractor.initialize(mContext, usageStats);
                 extractor.setConfig(config);
                 extractor.setZenHelper(zenHelper);
+                if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
+                        || restrictAudioAttributesCall()) {
+                    extractor.setCompatChangeLogger(platformCompat);
+                }
                 mSignalExtractors[i] = extractor;
             } catch (ClassNotFoundException e) {
                 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index fc106b8..86dcecf 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -287,4 +287,11 @@
             }
         }
     }
+
+    void destroy() {
+        if (mLauncherAppsCallbackRegistered) {
+            mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+            mLauncherAppsCallbackRegistered = false;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
new file mode 100644
index 0000000..2facab7
--- /dev/null
+++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
@@ -0,0 +1,175 @@
+/*
+ * 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.notification;
+
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.PrintWriter;
+import java.util.TreeSet;
+
+/**
+ * Handles canceling notifications when their time to live expires
+ */
+@FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+public class TimeToLiveHelper {
+    private static final String TAG = TimeToLiveHelper.class.getSimpleName();
+    private static final String ACTION = "com.android.server.notification.TimeToLiveHelper";
+
+    private static final int REQUEST_CODE_TIMEOUT = 1;
+    private static final String SCHEME_TIMEOUT = "timeout";
+    static final String EXTRA_KEY = "key";
+    private final Context mContext;
+    private final NotificationManagerPrivate mNm;
+    private final AlarmManager mAm;
+
+    @VisibleForTesting
+    final TreeSet<Pair<Long, String>> mKeys;
+
+    public TimeToLiveHelper(NotificationManagerPrivate nm, Context context) {
+        mContext = context;
+        mNm = nm;
+        mAm = context.getSystemService(AlarmManager.class);
+        mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first));
+
+        IntentFilter timeoutFilter = new IntentFilter(ACTION);
+        timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+        mContext.registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+                Context.RECEIVER_NOT_EXPORTED);
+    }
+
+    void destroy() {
+        mContext.unregisterReceiver(mNotificationTimeoutReceiver);
+    }
+
+    void dump(PrintWriter pw, String indent) {
+        pw.println(indent + "mKeys " + mKeys);
+    }
+
+    private @NonNull PendingIntent getAlarmPendingIntent(String nextKey, int flags) {
+        flags |= PendingIntent.FLAG_IMMUTABLE;
+        return PendingIntent.getBroadcast(mContext,
+                REQUEST_CODE_TIMEOUT,
+                new Intent(ACTION)
+                        .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+                        .setData(new Uri.Builder()
+                                .scheme(SCHEME_TIMEOUT)
+                                .appendPath(nextKey)
+                                .build())
+                        .putExtra(EXTRA_KEY, nextKey)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                flags);
+    }
+
+    @VisibleForTesting
+    void scheduleTimeoutLocked(NotificationRecord record, long currentTime) {
+        removeMatchingEntry(record.getKey());
+
+        final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter();
+        if (record.getNotification().getTimeoutAfter() > 0) {
+            final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first;
+
+            // Maybe replace alarm with an earlier one
+            if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) {
+                if (currentEarliestTime != null) {
+                    cancelFirstAlarm();
+                }
+                mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+                maybeScheduleFirstAlarm();
+            } else {
+                mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void cancelScheduledTimeoutLocked(NotificationRecord record) {
+        removeMatchingEntry(record.getKey());
+    }
+
+    private void removeMatchingEntry(String key) {
+        if (!mKeys.isEmpty() && key.equals(mKeys.first().second)) {
+            // cancel the first alarm, remove the first entry, maybe schedule the alarm for the new
+            // first entry
+            cancelFirstAlarm();
+            mKeys.remove(mKeys.first());
+            maybeScheduleFirstAlarm();
+        } else {
+            // just remove the entry
+            Pair<Long, String> trackedPair = null;
+            for (Pair<Long, String> entry : mKeys) {
+                if (key.equals(entry.second)) {
+                    trackedPair = entry;
+                    break;
+                }
+            }
+            if (trackedPair != null) {
+                mKeys.remove(trackedPair);
+            }
+        }
+    }
+
+    private void cancelFirstAlarm() {
+        final PendingIntent pi = getAlarmPendingIntent(mKeys.first().second, FLAG_CANCEL_CURRENT);
+        mAm.cancel(pi);
+    }
+
+    private void maybeScheduleFirstAlarm() {
+        if (!mKeys.isEmpty()) {
+            final PendingIntent piNewFirst = getAlarmPendingIntent(mKeys.first().second,
+                    FLAG_UPDATE_CURRENT);
+            mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    mKeys.first().first, piNewFirst);
+        }
+    }
+
+    @VisibleForTesting
+    final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+            if (ACTION.equals(action)) {
+                Pair<Long, String> earliest = mKeys.first();
+                String key = intent.getStringExtra(EXTRA_KEY);
+                if (!earliest.second.equals(key)) {
+                    Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking");
+                }
+                removeMatchingEntry(key);
+                mNm.timeoutNotification(earliest.second);
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4c653f6..fe9c3f2 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -50,6 +50,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.IShortcutService;
 import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutChangeCallback;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -151,6 +152,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
@@ -320,12 +322,11 @@
 
     private final Handler mHandler;
 
-    @GuardedBy("mLock")
-    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
+    private final CopyOnWriteArrayList<ShortcutChangeListener> mListeners =
+            new CopyOnWriteArrayList<>();
 
-    @GuardedBy("mLock")
-    private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
-            new ArrayList<>(1);
+    private final CopyOnWriteArrayList<ShortcutChangeCallback> mShortcutChangeCallbacks =
+            new CopyOnWriteArrayList<>();
 
     private final AtomicLong mRawLastResetTime = new AtomicLong(0);
 
@@ -1841,18 +1842,11 @@
             @UserIdInt final int userId) {
         return () -> {
             try {
-                final ArrayList<ShortcutChangeListener> copy;
-                synchronized (mLock) {
-                    if (!isUserUnlockedL(userId)) {
-                        return;
-                    }
-
-                    copy = new ArrayList<>(mListeners);
+                if (!isUserUnlockedL(userId)) {
+                    return;
                 }
                 // Note onShortcutChanged() needs to be called with the system service permissions.
-                for (int i = copy.size() - 1; i >= 0; i--) {
-                    copy.get(i).onShortcutChanged(packageName, userId);
-                }
+                mListeners.forEach(listener -> listener.onShortcutChanged(packageName, userId));
             } catch (Exception ignore) {
             }
         };
@@ -1867,22 +1861,17 @@
         final UserHandle user = UserHandle.of(userId);
         injectPostToHandler(() -> {
             try {
-                final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
-                synchronized (mLock) {
-                    if (!isUserUnlockedL(userId)) {
-                        return;
-                    }
-
-                    copy = new ArrayList<>(mShortcutChangeCallbacks);
+                if (!isUserUnlockedL(userId)) {
+                    return;
                 }
-                for (int i = copy.size() - 1; i >= 0; i--) {
+                mShortcutChangeCallbacks.forEach(callback -> {
                     if (!CollectionUtils.isEmpty(changedList)) {
-                        copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user);
+                        callback.onShortcutsAddedOrUpdated(packageName, changedList, user);
                     }
                     if (!CollectionUtils.isEmpty(removedList)) {
-                        copy.get(i).onShortcutsRemoved(packageName, removedList, user);
+                        callback.onShortcutsRemoved(packageName, removedList, user);
                     }
-                }
+                });
             } catch (Exception ignore) {
             }
         });
@@ -3425,17 +3414,13 @@
 
         @Override
         public void addListener(@NonNull ShortcutChangeListener listener) {
-            synchronized (mLock) {
-                mListeners.add(Objects.requireNonNull(listener));
-            }
+            mListeners.add(Objects.requireNonNull(listener));
         }
 
         @Override
         public void addShortcutChangeCallback(
                 @NonNull LauncherApps.ShortcutChangeCallback callback) {
-            synchronized (mLock) {
-                mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
-            }
+            mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
         }
 
         @Override
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
index b237ca2..84ed87a 100644
--- a/services/core/java/com/android/server/power/FaceDownDetector.java
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -76,6 +76,8 @@
     private static final boolean DEFAULT_FEATURE_ENABLED = true;
 
     private boolean mIsEnabled;
+    // Defaults to true, we only want to disable if this is specifically requested.
+    private boolean mEnabledOverride = true;
 
     private int mSensorMaxLatencyMicros;
 
@@ -240,6 +242,7 @@
         pw.println("  mZAccelerationThreshold=" + mZAccelerationThreshold);
         pw.println("  mAccelerationThreshold=" + mAccelerationThreshold);
         pw.println("  mTimeThreshold=" + mTimeThreshold);
+        pw.println("  mEnabledOverride=" + mEnabledOverride);
     }
 
     @Override
@@ -336,10 +339,9 @@
     }
 
     private boolean isEnabled() {
-        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
-                DEFAULT_FEATURE_ENABLED)
-                && mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_flipToScreenOffEnabled);
+        return mEnabledOverride && DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_FEATURE_ENABLED, DEFAULT_FEATURE_ENABLED) && mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_flipToScreenOffEnabled);
     }
 
     private float getAccelerationThreshold() {
@@ -450,6 +452,15 @@
     }
 
     /**
+     * Allows detector to be enabled & disabled.
+     * @param enabled whether to enable detector.
+     */
+    public void setEnabledOverride(boolean enabled) {
+        mEnabledOverride = enabled;
+        mIsEnabled = isEnabled();
+    }
+
+    /**
      * Sets how much screen on time might be saved as a result of this detector. Currently used for
      * logging purposes.
      */
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 53863aa..eb1f720 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -622,7 +622,6 @@
     // Value we store for tracking face down behavior.
     @VisibleForTesting
     boolean mIsFaceDown = false;
-    private boolean mUseFaceDownDetector = true;
     private long mLastFlipTime = 0L;
 
     // The screen brightness setting override from the window manager
@@ -3254,7 +3253,7 @@
                     mScreenTimeoutOverridePolicy.getScreenTimeoutOverrideLocked(
                             mWakeLockSummary, screenOffTimeout);
         }
-        if (mIsFaceDown && mUseFaceDownDetector) {
+        if (mIsFaceDown) {
             shortestScreenOffTimeout = Math.min(screenDimDuration, shortestScreenOffTimeout);
         }
 
@@ -4702,7 +4701,6 @@
             pw.println("  mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
             pw.println("  mLastFlipTime=" + mLastFlipTime);
             pw.println("  mIsFaceDown=" + mIsFaceDown);
-            pw.println("  mUseFaceDownDetector=" + mUseFaceDownDetector);
 
             pw.println();
             pw.println("Settings and Configuration:");
@@ -6927,7 +6925,7 @@
         public void setUseFaceDownDetector(boolean enable) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                mUseFaceDownDetector = enable;
+                mFaceDownDetector.setEnabledOverride(enable);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d060c7c..54cb9c9 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -4885,7 +4885,6 @@
         if (type == WAKE_TYPE_PARTIAL) {
             // Only care about partial wake locks, since full wake locks
             // will be canceled when the user puts the screen to sleep.
-            aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
             if (historyName == null) {
                 historyName = name;
             }
@@ -5205,20 +5204,14 @@
     }
 
     @GuardedBy("this")
-    void aggregateLastWakeupUptimeLocked(long elapsedRealtimeMs, long uptimeMs) {
+    public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
         if (mLastWakeupReason != null) {
             long deltaUptimeMs = uptimeMs - mLastWakeupUptimeMs;
             SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
             timer.add(deltaUptimeMs * 1000, 1, elapsedRealtimeMs); // time in in microseconds
             mFrameworkStatsLogger.kernelWakeupReported(deltaUptimeMs * 1000, mLastWakeupReason,
                     mLastWakeupElapsedTimeMs);
-            mLastWakeupReason = null;
         }
-    }
-
-    @GuardedBy("this")
-    public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
-        aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
         mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
         mLastWakeupReason = reason;
         mLastWakeupUptimeMs = uptimeMs;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index b1b2cc9..f53a1b0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -49,7 +49,6 @@
     private static final long ENERGY_UNSPECIFIED = -1;
     private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
     private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
-    private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
 
     interface Injector {
         Handler getHandler();
@@ -76,7 +75,6 @@
     private CpuScalingPolicies mCpuScalingPolicies;
     private PowerProfile mPowerProfile;
     private KernelCpuStatsReader mKernelCpuStatsReader;
-    private PowerStatsUidResolver mUidResolver;
     private ConsumedEnergyRetriever mConsumedEnergyRetriever;
     private IntSupplier mVoltageSupplier;
     private int mDefaultCpuPowerBrackets;
@@ -97,7 +95,8 @@
     private long[] mLastConsumedEnergyUws;
 
     public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+                injector.getClock());
         mInjector = injector;
     }
 
@@ -113,7 +112,6 @@
         mCpuScalingPolicies = mInjector.getCpuScalingPolicies();
         mPowerProfile = mInjector.getPowerProfile();
         mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader();
-        mUidResolver = mInjector.getUidResolver();
         mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
         mVoltageSupplier = mInjector.getVoltageSupplier();
         mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets();
@@ -421,7 +419,8 @@
 
         boolean nonzero = false;
         for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) {
-            long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket];
+            long delta = Math.max(0,
+                    timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket]);
             if (delta != 0) {
                 nonzero = true;
             }
@@ -447,6 +446,12 @@
         }
     }
 
+    @Override
+    protected void onUidRemoved(int uid) {
+        super.onUidRemoved(uid);
+        mUidStats.remove(uid);
+    }
+
     /**
      * Native class that retrieves CPU stats from the kernel.
      */
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 8c154e4..7bc6817 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -89,7 +89,6 @@
 
     private PowerStats mPowerStats;
     private long[] mDeviceStats;
-    private PowerStatsUidResolver mPowerStatsUidResolver;
     private volatile TelephonyManager mTelephonyManager;
     private LongSupplier mCallDurationSupplier;
     private LongSupplier mScanDurationSupplier;
@@ -106,7 +105,8 @@
     private long mLastScanDuration;
 
     public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+                injector.getClock());
         mInjector = injector;
     }
 
@@ -130,7 +130,6 @@
             return false;
         }
 
-        mPowerStatsUidResolver = mInjector.getUidResolver();
         mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
         mVoltageSupplier = mInjector.getVoltageSupplier();
 
@@ -310,7 +309,7 @@
                 continue;
             }
 
-            int uid = mPowerStatsUidResolver.mapUid(uidDelta.getUid());
+            int uid = mUidResolver.mapUid(uidDelta.getUid());
             long[] stats = mPowerStats.uidStats.get(uid);
             if (stats == null) {
                 stats = new long[mLayout.getUidStatsArrayLength()];
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index 5dd11db..b82c021 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -53,6 +53,7 @@
     private static final int MILLIVOLTS_PER_VOLT = 1000;
     private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
     private final Handler mHandler;
+    protected final PowerStatsUidResolver mUidResolver;
     protected final Clock mClock;
     private final long mThrottlePeriodMs;
     private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
@@ -63,9 +64,25 @@
     @SuppressWarnings("unchecked")
     private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
 
-    public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) {
+    public PowerStatsCollector(Handler handler, long throttlePeriodMs,
+            PowerStatsUidResolver uidResolver, Clock clock) {
         mHandler = handler;
         mThrottlePeriodMs = throttlePeriodMs;
+        mUidResolver = uidResolver;
+        mUidResolver.addListener(new PowerStatsUidResolver.Listener() {
+            @Override
+            public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+            }
+
+            @Override
+            public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+            }
+
+            @Override
+            public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+                mHandler.post(()->onUidRemoved(isolatedUid));
+            }
+        });
         mClock = clock;
     }
 
@@ -203,6 +220,9 @@
         done.block();
     }
 
+    protected void onUidRemoved(int uid) {
+    }
+
     /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
     protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
         // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 10e868d..c1d92cf 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -119,8 +119,13 @@
     }
 
     @Override
-    public void onStart() {
-        publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+    public void onStart() {}
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+            publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+        }
     }
 
     private void notifyTraceur(boolean sessionStolen) {
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index ed9fa65..3619253 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -16,8 +16,10 @@
 
 package com.android.server.vcn.routeselection;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -38,6 +40,10 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.server.vcn.VcnContext;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.BitSet;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
@@ -56,8 +62,51 @@
 public class IpSecPacketLossDetector extends NetworkMetricMonitor {
     private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
 
+    private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
+
+    // Ignore the packet loss detection result if the expected packet number is smaller than 10.
+    // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
+    // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PACKET_LOSS_UNAVALAIBLE = -1;
+    static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"PACKET_LOSS_"},
+            value = {
+                PACKET_LOSS_RATE_VALID,
+                PACKET_LOSS_RATE_INVALID,
+                PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
+            })
+    @Target({ElementType.TYPE_USE})
+    private @interface PacketLossResultType {}
+
+    /** Indicates a valid packet loss rate is available */
+    private static final int PACKET_LOSS_RATE_VALID = 0;
+
+    /**
+     * Indicates that the detector cannot get a valid packet loss rate due to one of the following
+     * reasons:
+     *
+     * <ul>
+     *   <li>The replay window did not proceed and thus all packets might have been delivered out of
+     *       order
+     *   <li>The expected received packet number is too small and thus the detection result is not
+     *       reliable
+     *   <li>There are unexpected errors
+     * </ul>
+     */
+    private static final int PACKET_LOSS_RATE_INVALID = 1;
+
+    /**
+     * The sequence number increase is unusually large and might be caused an intentional leap on
+     * the server's downlink
+     *
+     * <p>Inbound sequence number will not always increase consecutively. During load balancing the
+     * server might add a big leap on the sequence number intentionally. In such case a high packet
+     * loss rate does not always indicate a lossy network
+     */
+    private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;
 
     // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
     // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
@@ -68,8 +117,12 @@
 
     private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
 
+    // By default, there's no maximum limit enforced
+    private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
+
     private long mPollIpSecStateIntervalMs;
-    private final int mPacketLossRatePercentThreshold;
+    private int mPacketLossRatePercentThreshold;
+    private int mMaxSeqNumIncreasePerSecond;
 
     @NonNull private final Handler mHandler;
     @NonNull private final PowerManager mPowerManager;
@@ -108,6 +161,7 @@
 
         mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
         mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
 
         // Register for system broadcasts to monitor idle mode change
         final IntentFilter intentFilter = new IntentFilter();
@@ -172,6 +226,24 @@
         return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
+        int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+        if (Flags.handleSeqNumLeap() && carrierConfig != null) {
+            maxSeqNumIncrease =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
+                            MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
+        }
+
+        if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+            logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
+            return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+        }
+
+        return maxSeqNumIncrease;
+    }
+
     @Override
     protected void onSelectedUnderlyingNetworkChanged() {
         if (!isSelectedUnderlyingNetwork()) {
@@ -207,6 +279,11 @@
         // The already scheduled event will not be affected. The followup events will be scheduled
         // with the new interval
         mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+
+        if (Flags.handleSeqNumLeap()) {
+            mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+            mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
+        }
     }
 
     @Override
@@ -307,30 +384,40 @@
             return;
         }
 
-        final int packetLossRate =
+        final PacketLossCalculationResult calculateResult =
                 mPacketLossCalculator.getPacketLossRatePercentage(
-                        mLastIpSecTransformState, state, getLogPrefix());
+                        mLastIpSecTransformState,
+                        state,
+                        mMaxSeqNumIncreasePerSecond,
+                        getLogPrefix());
 
-        if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+        if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
             return;
         }
 
         final String logMsg =
-                "packetLossRate: "
-                        + packetLossRate
+                "calculateResult: "
+                        + calculateResult
                         + "% in the past "
                         + (state.getTimestampMillis()
                                 - mLastIpSecTransformState.getTimestampMillis())
                         + "ms";
 
         mLastIpSecTransformState = state;
-        if (packetLossRate < mPacketLossRatePercentThreshold) {
+        if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
             logV(logMsg);
+
+            // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
+            // the validation
             onValidationResultReceivedInternal(false /* isFailed */);
         } else {
             logInfo(logMsg);
-            onValidationResultReceivedInternal(true /* isFailed */);
 
+            if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
+                onValidationResultReceivedInternal(true /* isFailed */);
+            }
+
+            // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation
             if (Flags.validateNetworkOnIpsecLoss()) {
                 // Trigger re-validation of the underlying network; if it fails, the VCN will
                 // attempt to migrate away.
@@ -343,9 +430,10 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class PacketLossCalculator {
         /** Calculate the packet loss rate between two timestamps */
-        public int getPacketLossRatePercentage(
+        public PacketLossCalculationResult getPacketLossRatePercentage(
                 @NonNull IpSecTransformState oldState,
                 @NonNull IpSecTransformState newState,
+                int maxSeqNumIncreasePerSecond,
                 String logPrefix) {
             logVIpSecTransform("oldState", oldState, logPrefix);
             logVIpSecTransform("newState", newState, logPrefix);
@@ -359,7 +447,23 @@
             if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
                 // The replay window did not proceed and all packets might have been delivered out
                 // of order
-                return PACKET_LOSS_UNAVALAIBLE;
+                return PacketLossCalculationResult.invalid();
+            }
+
+            boolean isUnusualSeqNumLeap = false;
+
+            // Handle sequence number leap
+            if (Flags.handleSeqNumLeap()
+                    && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+                final long timeDiffMillis =
+                        newState.getTimestampMillis() - oldState.getTimestampMillis();
+                final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
+
+                // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
+                // isUnusualSeqNumLeap can never be true.
+                if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
+                    isUnusualSeqNumLeap = true;
+                }
             }
 
             // Get the expected packet count by assuming there is no packet loss. In this case, SA
@@ -381,15 +485,23 @@
                             + " actualPktCntDiff: "
                             + actualPktCntDiff);
 
+            if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
+                // The sample size is too small to ensure a reliable detection result
+                return PacketLossCalculationResult.invalid();
+            }
+
             if (expectedPktCntDiff < 0
                     || expectedPktCntDiff == 0
                     || actualPktCntDiff < 0
                     || actualPktCntDiff > expectedPktCntDiff) {
                 logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
-                return PACKET_LOSS_UNAVALAIBLE;
+                return PacketLossCalculationResult.invalid();
             }
 
-            return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+            final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+            return isUnusualSeqNumLeap
+                    ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
+                    : PacketLossCalculationResult.valid(percent);
         }
     }
 
@@ -409,4 +521,64 @@
     private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
         return BitSet.valueOf(state.getReplayBitmap()).cardinality();
     }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculationResult {
+        @PacketLossResultType private final int mResultType;
+        private final int mPacketLossRatePercent;
+
+        private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
+            mResultType = type;
+            mPacketLossRatePercent = percent;
+        }
+
+        /** Construct an instance that contains a valid packet loss rate */
+        public static PacketLossCalculationResult valid(int percent) {
+            return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
+        }
+
+        /** Construct an instance indicating the inability to get a valid packet loss rate */
+        public static PacketLossCalculationResult invalid() {
+            return new PacketLossCalculationResult(
+                    PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
+        }
+
+        /** Construct an instance indicating that there is an unusual sequence number leap */
+        public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
+            return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
+        }
+
+        @PacketLossResultType
+        public int getResultType() {
+            return mResultType;
+        }
+
+        public int getPacketLossRatePercent() {
+            return mPacketLossRatePercent;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mResultType, mPacketLossRatePercent);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof PacketLossCalculationResult)) {
+                return false;
+            }
+
+            final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
+            return mResultType == rhs.mResultType
+                    && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
+        }
+
+        @Override
+        public String toString() {
+            return "mResultType: "
+                    + mResultType
+                    + " | mPacketLossRatePercent: "
+                    + mPacketLossRatePercent;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index a1b212f..b9b1060 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -272,6 +272,11 @@
         }
     }
 
+    protected static void logE(String className, String msgWithPrefix) {
+        Slog.w(className, msgWithPrefix);
+        LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
+    }
+
     protected static void logWtf(String className, String msgWithPrefix) {
         Slog.wtf(className, msgWithPrefix);
         LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 5e34596..a821f545 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -35,6 +35,7 @@
 import android.provider.Settings;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
+import android.util.Slog;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
@@ -201,6 +202,7 @@
             ActivityManager.getService().killPackageDependents(packageName,
                     UserHandle.USER_ALL);
         } catch (RemoteException e) {
+            Slog.wtf(TAG, "failed to call killPackageDependents for " + packageName, e);
         }
     }
 
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 532ff98..dcf20f9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -186,9 +186,12 @@
                 }
                 onWebViewProviderChanged(mCurrentWebViewPackage);
             }
+        } catch (WebViewPackageMissingException e) {
+            Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
         } catch (Throwable t) {
-            // Log and discard errors at this stage as we must not crash the system server.
-            Slog.e(TAG, "error preparing webview provider from system server", t);
+            // We don't know a case when this should happen but we log and discard errors at this
+            // stage as we must not crash the system server.
+            Slog.wtf(TAG, "error preparing webview provider from system server", t);
         }
 
         if (getCurrentWebViewPackage() == null) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index fb338ba..993597e 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -247,9 +247,12 @@
                 attemptRepair();
             }
 
+        } catch (WebViewPackageMissingException e) {
+            Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
         } catch (Throwable t) {
-            // Log and discard errors at this stage as we must not crash the system server.
-            Slog.e(TAG, "error preparing webview provider from system server", t);
+            // We don't know a case when this should happen but we log and discard errors at this
+            // stage as we must not crash the system server.
+            Slog.wtf(TAG, "error preparing webview provider from system server", t);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 9647a62..521f64f 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -389,7 +389,11 @@
             return mCheckedOptions;
         }
 
-        /** Returns the {@link Runnable} object to clear options Animation. */
+        /**
+         * Returns the {@link Runnable} object to clear options Animation. Note that the runnable
+         * should not be executed inside a lock because the implementation of runnable holds window
+         * manager's lock.
+         */
         @Nullable
         public Runnable getClearOptionsAnimationRunnable() {
             return mClearOptionsAnimation;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83745ed..bf094ed 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4260,7 +4260,8 @@
                 PendingIntentRecord rec = apr.get();
                 if (rec != null) {
                     mAtmService.mPendingIntentController.cancelIntentSender(rec,
-                            false /* cleanActivity */);
+                            false /* cleanActivity */,
+                            PendingIntentRecord.CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED);
                 }
             }
             pendingResults = null;
@@ -4478,6 +4479,7 @@
         getDisplayContent().mOpeningApps.remove(this);
         getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mWmService.mSnapshotController.onAppRemoved(this);
+        mAtmService.mStartingProcessActivities.remove(this);
 
         mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
         mTaskSupervisor.mStoppingActivities.remove(this);
@@ -6220,6 +6222,10 @@
     }
 
     boolean shouldBeVisible() {
+        return shouldBeVisible(false /* ignoringKeyguard */);
+    }
+
+    boolean shouldBeVisible(boolean ignoringKeyguard) {
         final Task task = getTask();
         if (task == null) {
             return false;
@@ -6227,7 +6233,7 @@
 
         final boolean behindOccludedContainer = !task.shouldBeVisible(null /* starting */)
                 || task.getOccludingActivityAbove(this) != null;
-        return shouldBeVisible(behindOccludedContainer, false /* ignoringKeyguard */);
+        return shouldBeVisible(behindOccludedContainer, ignoringKeyguard);
     }
 
     void makeVisibleIfNeeded(ActivityRecord starting, boolean reportToClient) {
@@ -10509,8 +10515,7 @@
         if (!getTurnScreenOnFlag()) {
             return false;
         }
-        final Task rootTask = getRootTask();
-        return mCurrentLaunchCanTurnScreenOn && rootTask != null
+        return mCurrentLaunchCanTurnScreenOn
                 && mTaskSupervisor.getKeyguardController().checkKeyguardVisibility(this);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 182e1c1..2cccd33 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -523,8 +523,11 @@
     void onActivityLaunched(TaskInfo taskInfo, ActivityRecord r) {
         final SparseArray<ActivityInterceptorCallback> callbacks =
                 mService.getActivityInterceptorCallbacks();
-        ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
-                r::clearOptionsAnimationForSiblings);
+        final ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(() -> {
+            synchronized (mService.mGlobalLock) {
+                r.clearOptionsAnimationForSiblings();
+            }
+        });
         for (int i = 0; i < callbacks.size(); i++) {
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
             callback.onActivityLaunched(taskInfo, r.info, info);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a5687e6..d984fb1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -390,6 +390,9 @@
 
     final VisibleActivityProcessTracker mVisibleActivityProcessTracker;
 
+    /** The starting activities which are waiting for their processes to attach. */
+    final ArrayList<ActivityRecord> mStartingProcessActivities = new ArrayList<>();
+
     /* Global service lock used by the package the owns this service. */
     final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock();
     /**
@@ -3779,17 +3782,25 @@
                 }
                 EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
                         r.shortComponentName, Boolean.toString(isAutoEnter));
-                r.setPictureInPictureParams(params);
-                r.mAutoEnteringPip = isAutoEnter;
-                mRootWindowContainer.moveActivityToPinnedRootTask(r,
-                        null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
-                        transition);
-                // Continue the pausing process after entering pip.
-                if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
-                    r.getTask().schedulePauseActivity(r, false /* userLeaving */,
-                            false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
+
+                // Ensure the ClientTransactionItems are bundled for this operation.
+                deferWindowLayout();
+                try {
+                    r.setPictureInPictureParams(params);
+                    r.mAutoEnteringPip = isAutoEnter;
+                    mRootWindowContainer.moveActivityToPinnedRootTask(r,
+                            null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+                            transition);
+                    // Continue the pausing process after entering pip.
+                    if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
+                        r.getTask().schedulePauseActivity(r, false /* userLeaving */,
+                                false /* pauseImmediately */, true /* autoEnteringPip */,
+                                "auto-pip");
+                    }
+                    r.mAutoEnteringPip = false;
+                } finally {
+                    continueWindowLayout();
                 }
-                r.mAutoEnteringPip = false;
             }
         };
 
@@ -4323,6 +4334,9 @@
             if (mDemoteTopAppReasons != 0) {
                 pw.println("  mDemoteTopAppReasons=" + mDemoteTopAppReasons);
             }
+            if (!mStartingProcessActivities.isEmpty()) {
+                pw.println("  mStartingProcessActivities=" + mStartingProcessActivities);
+            }
         }
 
         if (!printedAnything) {
@@ -5170,6 +5184,13 @@
 
     void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
             String hostingType) {
+        if (!mStartingProcessActivities.contains(activity)) {
+            mStartingProcessActivities.add(activity);
+        } else if (mProcessNames.get(
+                activity.processName, activity.info.applicationInfo.uid) != null) {
+            // The process is already starting. Wait for it to attach.
+            return;
+        }
         try {
             if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
@@ -6166,7 +6187,20 @@
         @Override
         public void onProcessRemoved(String name, int uid) {
             synchronized (mGlobalLockWithoutBoost) {
-                mProcessNames.remove(name, uid);
+                final WindowProcessController proc = mProcessNames.remove(name, uid);
+                if (proc != null && !mStartingProcessActivities.isEmpty()) {
+                    for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
+                        final ActivityRecord r = mStartingProcessActivities.get(i);
+                        if (uid == r.info.applicationInfo.uid && name.equals(r.processName)) {
+                            Slog.w(TAG, proc + " is removed with pending start " + r);
+                            mStartingProcessActivities.remove(i);
+                            // If visible, finish it to avoid getting stuck on screen.
+                            if (r.isVisibleRequested()) {
+                                r.finishIfPossible("starting-proc-removed", false /* oomAdj */);
+                            }
+                        }
+                    }
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d709fa5..d0c6e15 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -811,6 +811,13 @@
         return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
     }
 
+    boolean shouldPauseTouch(WindowContainer wc) {
+        // Once the close transition is ready, it means the onBackInvoked callback has invoked, and
+        // app is ready to trigger next transition, no matter what it will be.
+        return mAnimationHandler.mComposed && mWaitTransitionFinish == null
+                && mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
+    }
+
     /**
      * Cleanup animation, this can either happen when legacy transition ready, or when the Shell
      * transition finish.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 739f76e..00d42e0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -197,6 +197,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -289,6 +290,9 @@
 
     static final float INVALID_DPI = 0.0f;
 
+    private final boolean mVisibleBackgroundUserEnabled =
+            UserManager.isVisibleBackgroundUsersEnabled();
+
     @IntDef(prefix = { "FORCE_SCALING_MODE_" }, value = {
             FORCE_SCALING_MODE_AUTO,
             FORCE_SCALING_MODE_DISABLED
@@ -1999,7 +2003,12 @@
         }
         // Update directly because the app which will change the orientation of display is ready.
         if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
-            sendNewConfiguration();
+            // Run rotation change on display thread. See Transition#shouldApplyOnDisplayThread().
+            mWmService.mH.post(() -> {
+                synchronized (mWmService.mGlobalLock) {
+                    sendNewConfiguration();
+                }
+            });
             return;
         }
         if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
@@ -2698,11 +2707,15 @@
      * Returns true if the specified UID has access to this display.
      */
     boolean hasAccess(int uid) {
-        int userId = UserHandle.getUserId(uid);
-        boolean isUserVisibleOnDisplay = mWmService.mUmInternal.isUserVisible(
-                userId, mDisplayId);
-        return mDisplay.hasAccess(uid)
-                && (userId == UserHandle.USER_SYSTEM || isUserVisibleOnDisplay);
+        if (!mDisplay.hasAccess(uid)) {
+            return false;
+        }
+        if (!mVisibleBackgroundUserEnabled) {
+            return true;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        return userId == UserHandle.USER_SYSTEM
+                || mWmService.mUmInternal.isUserVisible(userId, mDisplayId);
     }
 
     boolean isPrivate() {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 45cf10b..5aa0ed7 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -327,14 +327,14 @@
                 R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
         mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
                 R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
-        mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
-                R.dimen.config_letterboxHorizontalPositionMultiplier);
-        mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
-                R.dimen.config_letterboxVerticalPositionMultiplier);
-        mLetterboxBookModePositionMultiplier = mContext.getResources().getFloat(
-                R.dimen.config_letterboxBookModePositionMultiplier);
-        mLetterboxTabletopModePositionMultiplier = mContext.getResources().getFloat(
-                R.dimen.config_letterboxTabletopModePositionMultiplier);
+        setLetterboxHorizontalPositionMultiplier(mContext.getResources().getFloat(
+                R.dimen.config_letterboxHorizontalPositionMultiplier));
+        setLetterboxVerticalPositionMultiplier(mContext.getResources().getFloat(
+                R.dimen.config_letterboxVerticalPositionMultiplier));
+        setLetterboxBookModePositionMultiplier(mContext.getResources().getFloat(
+                R.dimen.config_letterboxBookModePositionMultiplier));
+        setLetterboxTabletopModePositionMultiplier(mContext.getResources()
+                .getFloat(R.dimen.config_letterboxTabletopModePositionMultiplier));
         mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsHorizontalReachabilityEnabled);
         mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
@@ -657,29 +657,8 @@
      * right side.
      */
     float getLetterboxHorizontalPositionMultiplier(boolean isInBookMode) {
-        if (isInBookMode) {
-            if (mLetterboxBookModePositionMultiplier < 0.0f
-                    || mLetterboxBookModePositionMultiplier > 1.0f) {
-                Slog.w(TAG,
-                        "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=true): "
-                        + mLetterboxBookModePositionMultiplier);
-                // Default to left position if invalid value is provided.
-                return 0.0f;
-            } else {
-                return mLetterboxBookModePositionMultiplier;
-            }
-        } else {
-            if (mLetterboxHorizontalPositionMultiplier < 0.0f
-                    || mLetterboxHorizontalPositionMultiplier > 1.0f) {
-                Slog.w(TAG,
-                        "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=false):"
-                        + mLetterboxBookModePositionMultiplier);
-                // Default to central position if invalid value is provided.
-                return 0.5f;
-            } else {
-                return mLetterboxHorizontalPositionMultiplier;
-            }
-        }
+        return isInBookMode ? mLetterboxBookModePositionMultiplier
+                : mLetterboxHorizontalPositionMultiplier;
     }
 
     /*
@@ -689,37 +668,28 @@
      * bottom side.
      */
     float getLetterboxVerticalPositionMultiplier(boolean isInTabletopMode) {
-        if (isInTabletopMode) {
-            return (mLetterboxTabletopModePositionMultiplier < 0.0f
-                    || mLetterboxTabletopModePositionMultiplier > 1.0f)
-                    // Default to top position if invalid value is provided.
-                    ? 0.0f : mLetterboxTabletopModePositionMultiplier;
-        } else {
-            return (mLetterboxVerticalPositionMultiplier < 0.0f
-                    || mLetterboxVerticalPositionMultiplier > 1.0f)
-                    // Default to central position if invalid value is provided.
-                    ? 0.5f : mLetterboxVerticalPositionMultiplier;
-        }
+        return isInTabletopMode ? mLetterboxTabletopModePositionMultiplier
+                : mLetterboxVerticalPositionMultiplier;
     }
 
     /**
-     * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
-     * or > 1, then it and a value of {@link
-     * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
-     * central position (0.5) is used.
+     * Overrides horizontal position of a center of the letterboxed app window.
+     *
+     * @throws IllegalArgumentException If given value < 0 or > 1.
      */
     void setLetterboxHorizontalPositionMultiplier(float multiplier) {
-        mLetterboxHorizontalPositionMultiplier = multiplier;
+        mLetterboxHorizontalPositionMultiplier = assertValidMultiplier(multiplier,
+                "mLetterboxHorizontalPositionMultiplier");
     }
 
     /**
-     * Overrides vertical position of a center of the letterboxed app window. If given value < 0
-     * or > 1, then it and a value of {@link
-     * com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier} are ignored and
-     * central position (0.5) is used.
+     * Overrides vertical position of a center of the letterboxed app window.
+     *
+     * @throws IllegalArgumentException If given value < 0 or > 1.
      */
     void setLetterboxVerticalPositionMultiplier(float multiplier) {
-        mLetterboxVerticalPositionMultiplier = multiplier;
+        mLetterboxVerticalPositionMultiplier = assertValidMultiplier(multiplier,
+                "mLetterboxVerticalPositionMultiplier");
     }
 
     /**
@@ -740,6 +710,28 @@
                 com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier);
     }
 
+    /**
+     * Sets tabletop mode position multiplier.
+     *
+     * @throws IllegalArgumentException If given value < 0 or > 1.
+     */
+    @VisibleForTesting
+    void setLetterboxTabletopModePositionMultiplier(float multiplier) {
+        mLetterboxTabletopModePositionMultiplier = assertValidMultiplier(multiplier,
+                "mLetterboxTabletopModePositionMultiplier");
+    }
+
+    /**
+     * Sets tabletop mode position multiplier.
+     *
+     * @throws IllegalArgumentException If given value < 0 or > 1.
+     */
+    @VisibleForTesting
+    void setLetterboxBookModePositionMultiplier(float multiplier) {
+        mLetterboxBookModePositionMultiplier = assertValidMultiplier(multiplier,
+                "mLetterboxBookModePositionMultiplier");
+    }
+
     /*
      * Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in
      * landscape device orientation.
@@ -1356,4 +1348,21 @@
     void resetUserAppAspectRatioFullscreenEnabled() {
         setUserAppAspectRatioFullscreenOverrideEnabled(false);
     }
+
+    /**
+     * Checks whether the multiplier is between [0,1].
+     *
+     * @param multiplierName sent in the exception if multiplier is invalid, for easier debugging.
+     *
+     * @return multiplier, if valid
+     * @throws IllegalArgumentException if outside bounds.
+     */
+    private float assertValidMultiplier(float multiplier, String multiplierName)
+            throws IllegalArgumentException {
+        if (multiplier < 0.0f || multiplier > 1.0f) {
+            throw new IllegalArgumentException("Trying to set " + multiplierName
+                    + " out of bounds: " + multiplier);
+        }
+        return multiplier;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3eea6ac..1e88fe4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -72,7 +72,6 @@
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
 import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -270,8 +269,6 @@
     private int mTmpTaskLayerRank;
     private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
 
-    private final AttachApplicationHelper mAttachApplicationHelper = new AttachApplicationHelper();
-
     private String mDestroyAllActivitiesReason;
     private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
         @Override
@@ -1838,11 +1835,39 @@
     }
 
     boolean attachApplication(WindowProcessController app) throws RemoteException {
-        try {
-            return mAttachApplicationHelper.process(app);
-        } finally {
-            mAttachApplicationHelper.reset();
+        final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities;
+        RemoteException remoteException = null;
+        boolean hasActivityStarted = false;
+        for (int i = activities.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = activities.get(i);
+            if (app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
+                // The attaching process does not match the starting activity.
+                continue;
+            }
+            // Consume the pending record.
+            activities.remove(i);
+            final TaskFragment tf = r.getTaskFragment();
+            if (tf == null || r.finishing || r.app != null
+                    // Ignore keyguard because the app may use show-when-locked when creating.
+                    || !r.shouldBeVisible(true /* ignoringKeyguard */)
+                    || !r.showToCurrentUser()) {
+                continue;
+            }
+            try {
+                final boolean canResume = r.isFocusable() && r == tf.topRunningActivity();
+                if (mTaskSupervisor.realStartActivityLocked(r, app, canResume,
+                        true /* checkConfig */)) {
+                    hasActivityStarted = true;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Exception in new process when starting " + r, e);
+                remoteException = e;
+            }
         }
+        if (remoteException != null) {
+            throw remoteException;
+        }
+        return hasActivityStarted;
     }
 
     /**
@@ -2378,6 +2403,14 @@
             return false;
         }
 
+        return resumeFocusedTasksTopActivitiesUnchecked(targetRootTask, target, targetOptions,
+                deferPause);
+    }
+
+    @VisibleForTesting
+    boolean resumeFocusedTasksTopActivitiesUnchecked(
+            Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
+            boolean deferPause) {
         boolean result = false;
         if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
                 || getTopDisplayFocusedRootTask() == targetRootTask)) {
@@ -3740,67 +3773,4 @@
             }
         }
     }
-
-    private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {
-        private boolean mHasActivityStarted;
-        private RemoteException mRemoteException;
-        private WindowProcessController mApp;
-        private ActivityRecord mTop;
-
-        void reset() {
-            mHasActivityStarted = false;
-            mRemoteException = null;
-            mApp = null;
-            mTop = null;
-        }
-
-        boolean process(WindowProcessController app) throws RemoteException {
-            mApp = app;
-            for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
-                getChildAt(displayNdx).forAllRootTasks(this);
-                if (mRemoteException != null) {
-                    throw mRemoteException;
-                }
-            }
-            if (!mHasActivityStarted) {
-                ensureActivitiesVisible();
-            }
-            return mHasActivityStarted;
-        }
-
-        @Override
-        public void accept(Task rootTask) {
-            if (mRemoteException != null) {
-                return;
-            }
-            if (rootTask.getVisibility(null /* starting */)
-                    == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
-                return;
-            }
-            mTop = rootTask.topRunningActivity();
-            rootTask.forAllActivities(this);
-        }
-
-        @Override
-        public boolean test(ActivityRecord r) {
-            if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard
-                    || r.app != null || mApp.mUid != r.info.applicationInfo.uid
-                    || !mApp.mName.equals(r.processName)) {
-                return false;
-            }
-
-            try {
-                if (mTaskSupervisor.realStartActivityLocked(r, mApp,
-                        mTop == r && r.getTask().canBeResumed(r) /* andResume */,
-                        true /* checkConfig */)) {
-                    mHasActivityStarted = true;
-                }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Exception in new application when starting activity " + mTop, e);
-                mRemoteException = e;
-                return true;
-            }
-            return false;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ed88b5a..143605a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2446,6 +2446,9 @@
             ProtoLog.i(WM_DEBUG_SCREEN_ON,
                     "Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
                             viewVisibility, new RuntimeException().fillInStackTrace());
+            if (becameVisible) {
+                onWindowVisible(win);
+            }
 
             win.setDisplayLayoutNeeded();
             win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
@@ -10168,7 +10171,7 @@
      * Called to notify WMS that the specified window has become visible. This shows a Toast if the
      * window is deemed to hold sensitive content.
      */
-    void onWindowVisible(@NonNull WindowState w) {
+    private void onWindowVisible(@NonNull WindowState w) {
         showToastIfBlockingScreenCapture(w);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 731184f..1f06bfa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -848,7 +848,12 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+            try {
+                mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+            } catch (IllegalArgumentException  e) {
+                getErrPrintWriter().println("Error: invalid multiplier value " + e);
+                return -1;
+            }
         }
         return 0;
     }
@@ -867,7 +872,12 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+            try {
+                mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+            } catch (IllegalArgumentException  e) {
+                getErrPrintWriter().println("Error: invalid multiplier value " + e);
+                return -1;
+            }
         }
         return 0;
     }
@@ -1539,9 +1549,9 @@
         pw.println("        both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
         pw.println("        be ignored and framework implementation will determine aspect ratio.");
         pw.println("      --cornerRadius radius");
-        pw.println("        Corners radius for activities in the letterbox mode. If radius < 0,");
-        pw.println("        both it and R.integer.config_letterboxActivityCornersRadius will be");
-        pw.println("        ignored and corners of the activity won't be rounded.");
+        pw.println("        Corners radius (in pixels) for activities in the letterbox mode.");
+        pw.println("        If radius < 0, both R.integer.config_letterboxActivityCornersRadius");
+        pw.println("        and it will be ignored and corners of the activity won't be rounded.");
         pw.println("      --backgroundType [reset|solid_color|app_color_background");
         pw.println("          |app_color_background_floating|wallpaper]");
         pw.println("        Type of background used in the letterbox mode.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1ad1be..8c9317a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -69,6 +69,7 @@
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -829,18 +830,20 @@
     }
 
     private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
+        final boolean wasPrevFocusableAndVisible = tr.isFocusableAndVisible();
+
         int effects = applyChanges(tr, c);
         final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
             if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
-                effects = TRANSACT_EFFECTS_LIFECYCLE;
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
             }
         }
 
         if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
             tr.setForceTranslucent(c.getForceTranslucent());
-            effects = TRANSACT_EFFECTS_LIFECYCLE;
+            effects |= TRANSACT_EFFECTS_LIFECYCLE;
         }
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
@@ -873,8 +876,17 @@
                 boolean canEnterPip = activity.checkEnterPictureInPictureState(
                         "applyTaskChanges", true /* beforeStopping */);
                 if (canEnterPip) {
-                    canEnterPip = mService.mActivityClientController
-                            .requestPictureInPictureMode(activity);
+                    mService.mTaskSupervisor.beginDeferResume();
+                    try {
+                        canEnterPip = mService.mActivityClientController
+                                .requestPictureInPictureMode(activity);
+                    } finally {
+                        mService.mTaskSupervisor.endDeferResume();
+                        if (canEnterPip && !isPip2ExperimentEnabled()) {
+                            // Wait until the transaction is applied to only resume once.
+                            effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                        }
+                    }
                 }
                 if (!canEnterPip) {
                     // Restore the flag to its previous state when the activity cannot enter PIP.
@@ -883,6 +895,11 @@
             }
         }
 
+        // Activity in this Task may resume/pause when enter/exit pip.
+        if (wasPrevFocusableAndVisible != tr.isFocusableAndVisible()) {
+            effects |= TRANSACT_EFFECTS_LIFECYCLE;
+        }
+
         return effects;
     }
 
@@ -948,7 +965,7 @@
         }
         if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
             taskFragment.setForceTranslucent(c.getForceTranslucent());
-            effects = TRANSACT_EFFECTS_LIFECYCLE;
+            effects |= TRANSACT_EFFECTS_LIFECYCLE;
         }
 
         effects |= applyChanges(taskFragment, c);
@@ -2293,6 +2310,9 @@
         TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
         taskFragment.setTaskFragmentOrganizer(organizerToken,
                 ownerActivity.getUid(), ownerActivity.info.processName);
+        if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
+            taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+        }
         final int position;
         if (creationParams.getPairedPrimaryFragmentToken() != null) {
             // When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4d9fc6c..2fcee50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,7 +28,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.permission.flags.Flags.sensitiveContentImprovements;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
@@ -2139,9 +2138,6 @@
                 }
             }
             setDisplayLayoutNeeded();
-            if (sensitiveContentImprovements() && visible) {
-                mWmService.onWindowVisible(this);
-            }
         }
     }
 
@@ -2974,6 +2970,11 @@
             return true;
         }
 
+        // Do not allow back predictive animation target to receive touch, app can trigger an
+        // unexpected transition so basically unable to polish it.
+        if (mWmService.mAtmService.mBackNavigationController.shouldPauseTouch(mActivityRecord)) {
+            return false;
+        }
         return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
                 && mActivityRecord.isVisibleRequested();
     }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 88c47f3..2f880ba 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -128,7 +128,8 @@
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
     jmethodID getInputPortAssociations;
-    jmethodID getInputUniqueIdAssociations;
+    jmethodID getInputUniqueIdAssociationsByPort;
+    jmethodID getInputUniqueIdAssociationsByDescriptor;
     jmethodID getDeviceTypeAssociations;
     jmethodID getKeyboardLayoutAssociations;
     jmethodID getHoverTapTimeout;
@@ -634,10 +635,13 @@
         env->DeleteLocalRef(portAssociations);
     }
 
-    outConfig->uniqueIdAssociations =
-            readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
-                                                                 .getInputUniqueIdAssociations,
-                                                         "getInputUniqueIdAssociations");
+    outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+            std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
+                         "getInputUniqueIdAssociationsByPort");
+
+    outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
+            std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
+                         "getInputUniqueIdAssociationsByDescriptor");
 
     outConfig->deviceTypeAssociations =
             readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
@@ -3090,8 +3094,11 @@
     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;");
 
     GET_METHOD_ID(gServiceClassInfo.getDeviceTypeAssociations, clazz, "getDeviceTypeAssociations",
                   "()[Ljava/lang/String;");
diff --git a/services/core/jni/linux/usb/f_accessory.h b/services/core/jni/linux/usb/f_accessory.h
new file mode 100644
index 0000000..abd864c
--- /dev/null
+++ b/services/core/jni/linux/usb/f_accessory.h
@@ -0,0 +1,34 @@
+/*
+ * This file is auto-generated. Modifications will be lost.
+ *
+ * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/
+ * for more information.
+ */
+#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H
+#define _UAPI_LINUX_USB_F_ACCESSORY_H
+#define USB_ACCESSORY_VENDOR_ID 0x18D1
+#define USB_ACCESSORY_PRODUCT_ID 0x2D00
+#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01
+#define ACCESSORY_STRING_MANUFACTURER 0
+#define ACCESSORY_STRING_MODEL 1
+#define ACCESSORY_STRING_DESCRIPTION 2
+#define ACCESSORY_STRING_VERSION 3
+#define ACCESSORY_STRING_URI 4
+#define ACCESSORY_STRING_SERIAL 5
+#define ACCESSORY_GET_PROTOCOL 51
+#define ACCESSORY_SEND_STRING 52
+#define ACCESSORY_START 53
+#define ACCESSORY_REGISTER_HID 54
+#define ACCESSORY_UNREGISTER_HID 55
+#define ACCESSORY_SET_HID_REPORT_DESC 56
+#define ACCESSORY_SEND_HID_EVENT 57
+#define ACCESSORY_SET_AUDIO_MODE 58
+#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256])
+#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256])
+#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256])
+#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256])
+#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256])
+#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256])
+#define ACCESSORY_IS_START_REQUESTED _IO('M', 7)
+#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8)
+#endif
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index bb46c44..73e53e2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -28,7 +28,6 @@
 import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
 import android.util.Slog;
 
@@ -151,7 +150,7 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+    public void onUiCancellation(boolean isUserCancellation) {
         // Not needed since UI is not involved
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3513cb5..9781fb9 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -31,7 +31,6 @@
 import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
@@ -164,7 +163,7 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+    public void onUiCancellation(boolean isUserCancellation) {
         String exception = CreateCredentialException.TYPE_USER_CANCELED;
         String message = "User cancelled the selector";
         if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index bfa2d61..e73dacb 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.credentials;
 
-import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
-
 import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -82,25 +80,18 @@
                 UserSelectionDialogResult selection = UserSelectionDialogResult
                         .fromResultData(resultData);
                 if (selection != null) {
-                    ResultReceiver resultReceiver = resultData.getParcelable(
-                            EXTRA_FINAL_RESPONSE_RECEIVER,
-                            ResultReceiver.class);
-                    mCallbacks.onUiSelection(selection, resultReceiver);
+                    mCallbacks.onUiSelection(selection);
                 }
                 break;
             case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
 
                 mStatus = UiStatus.TERMINATED;
-                mCallbacks.onUiCancellation(/* isUserCancellation= */ true,
-                        resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
-                                ResultReceiver.class));
+                mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
                 break;
             case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
 
                 mStatus = UiStatus.TERMINATED;
-                mCallbacks.onUiCancellation(/* isUserCancellation= */ false,
-                        resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
-                                ResultReceiver.class));
+                mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
                 break;
             case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
                 mStatus = UiStatus.TERMINATED;
@@ -124,10 +115,10 @@
      */
     public interface CredentialManagerUiCallback {
         /** Called when the user makes a selection. */
-        void onUiSelection(UserSelectionDialogResult selection, ResultReceiver resultReceiver);
+        void onUiSelection(UserSelectionDialogResult selection);
 
         /** Called when the UI is canceled without a successful provider result. */
-        void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver);
+        void onUiCancellation(boolean isUserCancellation);
 
         /** Called when the selector UI fails to come up (mostly due to parsing issue today). */
         void onUiSelectorInvocationFailure();
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 69d32a0..b1673e2 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -26,6 +26,7 @@
 import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCandidateCredentialsException;
 import android.credentials.GetCandidateCredentialsResponse;
+import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialRequest;
 import android.credentials.GetCredentialResponse;
 import android.credentials.IGetCandidateCredentialsCallback;
@@ -64,6 +65,8 @@
     private final int mAutofillSessionId;
     private final int mAutofillRequestId;
 
+    private final ResultReceiver mAutofillCallback;
+
     public GetCandidateRequestSession(
             Context context, SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
@@ -77,6 +80,8 @@
         mClientBinder = clientBinder;
         mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1);
         mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1);
+        mAutofillCallback = request.getData().getParcelable(
+                CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, ResultReceiver.class);
         if (mClientBinder != null) {
             setUpClientCallbackListener(mClientBinder);
         }
@@ -155,34 +160,34 @@
     public void onFinalErrorReceived(ComponentName componentName, String errorType,
             String message) {
         Slog.d(TAG, "onFinalErrorReceived");
-        respondToFinalReceiverWithFailureAndFinish(this.mFinalResponseReceiver, errorType, message);
+        if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
+            Slog.d(TAG, "User canceled but session is not being terminated");
+            return;
+        }
+        respondToFinalReceiverWithFailureAndFinish(errorType, message);
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation,
-            @Nullable ResultReceiver finalResponseReceiver) {
-        String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
-        String message = "User cancelled the selector";
-        if (!isUserCancellation) {
-            exception = GetCandidateCredentialsException.TYPE_INTERRUPTED;
-            message = "The UI was interrupted - please try again.";
-        }
-        mRequestSessionMetric.collectFrameworkException(exception);
-        respondToFinalReceiverWithFailureAndFinish(finalResponseReceiver, exception, message);
+    public void onUiCancellation(boolean isUserCancellation) {
+        Slog.d(TAG, "User canceled but session is not being terminated");
     }
 
     private void respondToFinalReceiverWithFailureAndFinish(
-            ResultReceiver finalResponseReceiver,
             String exception, String message
     ) {
-        if (finalResponseReceiver != null) {
+        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+            Slog.w(TAG, "Request has already been completed. This is strange.");
+            return;
+        }
+
+        if (mAutofillCallback != null) {
             Bundle resultData = new Bundle();
             resultData.putStringArray(
                     CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
                     new String[] {exception, message});
-            finalResponseReceiver.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
+            mAutofillCallback.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
         } else {
-            Slog.w(TAG, "onUiCancellation called but finalResponseReceiver not found");
+            Slog.w(TAG, "onUiCancellation called but mAutofillCallback not found");
         }
         finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode());
     }
@@ -219,12 +224,25 @@
     public void onFinalResponseReceived(ComponentName componentName,
             GetCredentialResponse response) {
         Slog.d(TAG, "onFinalResponseReceived");
-        if (this.mFinalResponseReceiver != null) {
+        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+            Slog.w(TAG, "Request has already been completed. This is strange.");
+            return;
+        }
+        respondToFinalReceiverWithResponseAndFinish(response);
+    }
+
+    private void respondToFinalReceiverWithResponseAndFinish(GetCredentialResponse response) {
+        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+            Slog.w(TAG, "Request has already been completed. This is strange.");
+            return;
+        }
+
+        if (this.mAutofillCallback != null) {
             Slog.d(TAG, "onFinalResponseReceived sending through final receiver");
             Bundle resultData = new Bundle();
             resultData.putParcelable(
                     CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
-            mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
+            mAutofillCallback.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
             finishSession(/*propagateCancellation=*/ false, ApiStatus.SUCCESS.getMetricCode());
         } else {
             Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry");
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c26229b..be36b6c 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -32,7 +32,6 @@
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
@@ -166,7 +165,7 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+    public void onUiCancellation(boolean isUserCancellation) {
         String exception = GetCredentialException.TYPE_USER_CANCELED;
         String message = "User cancelled the selector";
         if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 054ba2b..c0bc8e0 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -17,7 +17,6 @@
 package com.android.server.credentials;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -35,7 +34,6 @@
 import android.os.IInterface;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.service.credentials.CallingAppInfo;
 import android.util.Slog;
@@ -104,9 +102,6 @@
 
     protected PendingIntent mPendingIntent;
 
-    @Nullable
-    protected ResultReceiver mFinalResponseReceiver;
-
     @NonNull
     protected RequestSessionStatus mRequestSessionStatus =
             RequestSessionStatus.IN_PROGRESS;
@@ -225,8 +220,7 @@
     // UI callbacks
 
     @Override // from CredentialManagerUiCallbacks
-    public void onUiSelection(UserSelectionDialogResult selection,
-            ResultReceiver finalResponseReceiver) {
+    public void onUiSelection(UserSelectionDialogResult selection) {
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Slog.w(TAG, "Request has already been completed. This is strange.");
             return;
@@ -242,7 +236,7 @@
             Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
             return;
         }
-        mFinalResponseReceiver = finalResponseReceiver;
+
         ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric;
         int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric()
                 .size();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 94c1374..d1830126 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -215,6 +215,9 @@
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
+        if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) {
+            return false;
+        }
         final String metadataKey = sActionToMetadataKeyMap.get(provisioningAction);
         return packageInfo.applicationInfo.metaData.getBoolean(metadataKey);
     }
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 b0eee08..1666fef 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1708,7 +1708,6 @@
      * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
      */
     @Test
-    @FlakyTest(bugId = 127687569)
     public void testCreateVirtualDisplay_setSurface() throws Exception {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 783971a..89b48ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,9 +16,18 @@
 
 package com.android.server.am;
 
+import static android.os.Process.INVALID_UID;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_NULL;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_ONE_SHOT_SENT;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +43,7 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.os.Looper;
+import android.os.UserHandle;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -54,6 +64,7 @@
     private static final String TEST_PACKAGE_NAME = "test-package-1";
     private static final String TEST_FEATURE_ID = "test-feature-1";
     private static final int TEST_CALLING_UID = android.os.Process.myUid();
+    private static final int TEST_USER_ID = 0;
     private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
 
     @Mock
@@ -92,7 +103,7 @@
 
     private PendingIntentRecord createPendingIntentRecord(int flags) {
         return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
-                TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, 0, null, null, 0,
+                TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0,
                 TEST_INTENTS, null, flags, null);
     }
 
@@ -126,6 +137,54 @@
                 piCaptor.getValue().getTarget());
     }
 
+    @Test
+    public void testCancellationReason() {
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            assertCancelReason(CANCEL_REASON_NULL, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            mPendingIntentController.cancelIntentSender(pir);
+            assertCancelReason(CANCEL_REASON_OWNER_CANCELED, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+            assertCancelReason(CANCEL_REASON_SUPERSEDED, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+            pir.send(0, null, null, null, null, null, null);
+            assertCancelReason(CANCEL_REASON_ONE_SHOT_SENT, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            mPendingIntentController.removePendingIntentsForPackage(TEST_PACKAGE_NAME,
+                    TEST_USER_ID, UserHandle.getAppId(TEST_CALLING_UID), true,
+                    CANCEL_REASON_OWNER_FORCE_STOPPED);
+            assertCancelReason(CANCEL_REASON_OWNER_FORCE_STOPPED, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            mPendingIntentController.removePendingIntentsForPackage(null,
+                    TEST_USER_ID, INVALID_UID, true,
+                    CANCEL_REASON_USER_STOPPED);
+            assertCancelReason(CANCEL_REASON_USER_STOPPED, pir.cancelReason);
+        }
+    }
+
+    private void assertCancelReason(int expectedReason, int actualReason) {
+        final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
+                + "; Actual: " + cancelReasonToString(actualReason);
+        assertEquals(errMsg, expectedReason, actualReason);
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index ad29392..d51828e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -67,12 +67,11 @@
     private static final int UID_2 = 99;
     private final MockClock mMockClock = new MockClock();
     private final HandlerThread mHandlerThread = new HandlerThread("test");
+    private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
     private Handler mHandler;
     private PowerStats mCollectedStats;
     private PowerProfile mPowerProfile = new PowerProfile();
     @Mock
-    private PowerStatsUidResolver mUidResolver;
-    @Mock
     private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
     @Mock
     private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
@@ -144,15 +143,8 @@
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
         when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true);
-        when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
-            int uid = invocation.getArgument(0);
-            if (uid == ISOLATED_UID) {
-                return UID_2;
-            } else {
-                return uid;
-            }
-        });
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]);
+        mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
     }
 
     @Test
@@ -268,8 +260,7 @@
         mockEnergyConsumers();
 
         CpuPowerStatsCollector collector = createCollector(8, 0);
-        CpuPowerStatsLayout layout =
-                new CpuPowerStatsLayout();
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
         layout.fromExtras(collector.getPowerStatsDescriptor().extras);
 
         mockKernelCpuStats(new long[]{1111, 2222, 3333},
@@ -333,6 +324,45 @@
                 .isEqualTo(78);
     }
 
+    @Test
+    public void isolatedUidReuse() {
+        mockCpuScalingPolicies(1);
+        mockPowerProfile();
+        mockEnergyConsumers();
+
+        CpuPowerStatsCollector collector = createCollector(8, 0);
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
+        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+
+        mockKernelCpuStats(new long[]{1111, 2222, 3333},
+                new SparseArray<>() {{
+                    put(UID_2, new long[]{100, 150});
+                    put(ISOLATED_UID, new long[]{10000, 20000});
+                }}, 0, 1234);
+
+        mMockClock.uptime = 1000;
+        collector.forceSchedule();
+        waitForIdle();
+
+        mUidResolver.noteIsolatedUidRemoved(ISOLATED_UID, UID_2);
+        mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
+
+        mockKernelCpuStats(new long[]{5555, 4444, 3333},
+                new SparseArray<>() {{
+                    put(UID_2, new long[]{100, 150});
+                    put(ISOLATED_UID, new long[]{245, 528});
+                }}, 1234, 3421);
+
+        mMockClock.uptime = 2000;
+        collector.forceSchedule();
+        waitForIdle();
+
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0))
+                .isEqualTo(245);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1))
+                .isEqualTo(528);
+    }
+
     private void mockCpuScalingPolicies(int clusterCount) {
         SparseArray<int[]> cpus = new SparseArray<>();
         SparseArray<int[]> freqs = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index df1200b..89d6c1c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -66,8 +66,7 @@
     public void setup() {
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
-        mCollector = new PowerStatsCollector(mHandler,
-                60000,
+        mCollector = new PowerStatsCollector(mHandler, 60000, mock(PowerStatsUidResolver.class),
                 mMockClock) {
             @Override
             protected PowerStats collectStats() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 7e04277..90b131a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,10 +16,10 @@
 
 package com.android.server.biometrics;
 
-import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
@@ -65,8 +65,6 @@
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -204,43 +202,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
-    public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
-        final int fingerprintId = 0;
-        final int fingerprintStrength = 15;
-
-        final int faceId = 1;
-        final int faceStrength = 15;
-
-        final String[] config = {
-                // ID0:Fingerprint:Strong
-                String.format("%d:2:%d", fingerprintId, fingerprintStrength),
-                // ID2:Face:Convenience
-                String.format("%d:8:%d", faceId, faceStrength)
-        };
-
-        when(mInjector.getConfiguration(any())).thenReturn(config);
-
-        mAuthService = new AuthService(mContext, mInjector);
-        mAuthService.onStart();
-
-        verify(mFingerprintService).registerAuthenticators(mFingerprintPropsCaptor.capture());
-        final FingerprintSensorPropertiesInternal fingerprintProp =
-                mFingerprintPropsCaptor.getValue().get(0);
-        assertEquals(fingerprintProp.sensorId, fingerprintId);
-        assertEquals(fingerprintProp.sensorStrength,
-                Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
-
-        verify(mFaceService).registerAuthenticators(mFacePropsCaptor.capture());
-        final FaceSensorPropertiesInternal faceProp = mFacePropsCaptor.getValue().get(0);
-        assertEquals(faceProp.sensorId, faceId);
-        assertEquals(faceProp.sensorStrength,
-                Utils.authenticatorStrengthToPropertyStrength(faceStrength));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
-    public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+    public void testRegisterAuthenticator_registerAuthenticators() throws RemoteException {
         final int fingerprintId = 0;
         final int fingerprintStrength = 15;
 
@@ -265,7 +227,7 @@
         mFingerprintLooper.dispatchAll();
         mFaceLooper.dispatchAll();
 
-        verify(mFingerprintService).registerAuthenticatorsLegacy(
+        verify(mFingerprintService).registerAuthenticators(
                 mFingerprintSensorConfigurationsCaptor.capture());
 
         final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
@@ -275,8 +237,7 @@
         assertEquals(fingerprintProp[0].commonProps.sensorStrength,
                 Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
 
-        verify(mFaceService).registerAuthenticatorsLegacy(
-                mFaceSensorConfigurationsCaptor.capture());
+        verify(mFaceService).registerAuthenticators(mFaceSensorConfigurationsCaptor.capture());
 
         final android.hardware.biometrics.face.SensorProps[] faceProp =
                 mFaceSensorConfigurationsCaptor.getValue()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 5fd29c2..503ab8e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -76,7 +76,6 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
@@ -90,7 +89,6 @@
 import android.view.DisplayInfo;
 import android.view.WindowManager;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -246,14 +244,8 @@
         when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
         when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
         when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
-
-        if (com.android.server.biometrics.Flags.deHidl()) {
-            when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
-                    new Handler(TestableLooper.get(this).getLooper()));
-        } else {
-            when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
-                    new Handler(Looper.getMainLooper()));
-        }
+        when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                new Handler(TestableLooper.get(this).getLooper()));
 
         final String[] config = {
                 "0:2:15",  // ID0:Fingerprint:Strong
@@ -2037,11 +2029,7 @@
     }
 
     private void waitForIdle() {
-        if (com.android.server.biometrics.Flags.deHidl()) {
-            TestableLooper.get(this).processAllMessages();
-        } else {
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        }
+        TestableLooper.get(this).processAllMessages();
     }
 
     private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index c7300bb..960357f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -87,8 +87,12 @@
     private ISessionListener mSessionListener;
     @Mock
     private WindowManager mWindowManager;
+    @Mock
+    private Consumer<OperationContext> mStartHalConsumer;
 
-    private OperationContextExt mOpContext = new OperationContextExt(true);
+    private final FingerprintAuthenticateOptions mAuthenticateOptions =
+            new FingerprintAuthenticateOptions.Builder().build();
+    private final OperationContextExt mOpContext = new OperationContextExt(true);
     private IBiometricContextListener mListener;
     private BiometricContextProvider mProvider;
 
@@ -200,11 +204,11 @@
         final List<Integer> actual = new ArrayList<>();
         final List<Integer> expected = List.of(FoldState.FULLY_CLOSED, FoldState.FULLY_OPENED,
                 FoldState.UNKNOWN, FoldState.HALF_OPENED);
-        mProvider.subscribe(mOpContext, ctx -> {
+        mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
             assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
             assertThat(mProvider.getFoldState()).isEqualTo(ctx.foldState);
             actual.add(ctx.foldState);
-        });
+        }, mAuthenticateOptions);
 
         for (int v : expected) {
             mListener.onFoldChanged(v);
@@ -228,11 +232,11 @@
                 AuthenticateOptions.DISPLAY_STATE_NO_UI,
                 AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
 
-        mProvider.subscribe(mOpContext, ctx -> {
+        mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
             assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
             assertThat(mProvider.getDisplayState()).isEqualTo(ctx.displayState);
             actual.add(ctx.displayState);
-        });
+        }, mAuthenticateOptions);
 
         for (int v : expected) {
             mListener.onDisplayStateChanged(v);
@@ -250,11 +254,11 @@
     public void testSubscribesToAod() throws RemoteException {
         final List<Boolean> actual = new ArrayList<>();
 
-        mProvider.subscribe(mOpContext, ctx -> {
+        mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
             assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
             assertThat(mProvider.isAod()).isEqualTo(ctx.isAod);
             actual.add(ctx.isAod);
-        });
+        }, mAuthenticateOptions);
 
         for (int v : List.of(
                 AuthenticateOptions.DISPLAY_STATE_AOD,
@@ -273,10 +277,10 @@
     public void testSubscribesToAwake() throws RemoteException {
         final List<Boolean> actual = new ArrayList<>();
 
-        mProvider.subscribe(mOpContext, ctx -> {
+        mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
             assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
             actual.add(mProvider.isAwake());
-        });
+        }, mAuthenticateOptions);
 
         for (int v : List.of(
                 AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
@@ -295,14 +299,15 @@
     public void testSubscribesWithDifferentState() throws RemoteException {
         final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
         mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
-        mProvider.subscribe(mOpContext, nonEmptyConsumer);
-        verify(nonEmptyConsumer).accept(same(mOpContext.toAidlContext()));
+        mProvider.subscribe(mOpContext, mStartHalConsumer, nonEmptyConsumer, mAuthenticateOptions);
+
+        assertThat(mOpContext.getDisplayState()).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD);
     }
 
     @Test
     public void testUnsubscribes() throws RemoteException {
         final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
-        mProvider.subscribe(mOpContext, emptyConsumer);
+        mProvider.subscribe(mOpContext, mStartHalConsumer, emptyConsumer, mAuthenticateOptions);
         mProvider.unsubscribe(mOpContext);
 
         mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
@@ -311,7 +316,7 @@
         mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_UNKNOWN);
 
         final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
-        mProvider.subscribe(mOpContext, nonEmptyConsumer);
+        mProvider.subscribe(mOpContext, mStartHalConsumer, nonEmptyConsumer, mAuthenticateOptions);
         mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
         mProvider.unsubscribe(mOpContext);
         mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_NO_UI);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 971323a..fc573d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -52,13 +52,12 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
-import android.testing.TestableLooper;
 import android.util.Slog;
 
 import androidx.annotation.NonNull;
@@ -66,7 +65,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
@@ -79,7 +77,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -90,7 +89,6 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BiometricSchedulerTest {
 
     private static final String TAG = "BiometricSchedulerTest";
@@ -105,6 +103,9 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
             DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
     private BiometricScheduler<IFingerprint, ISession> mScheduler;
     private IBinder mToken;
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -121,8 +122,10 @@
                 mUsersStoppedCount++;
                 mCurrentUserId = UserHandle.USER_NULL;
             };
+    private TestLooper mLooper;
     private boolean mStartOperationsFinish = true;
     private int mStartUserClientCount = 0;
+
     @Mock
     private IBiometricService mBiometricService;
     @Mock
@@ -140,44 +143,39 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
         mToken = new Binder();
+        mLooper = new TestLooper();
+
         when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
                 BIOMETRIC_SUCCESS);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
-        if (Flags.deHidl()) {
-            mScheduler = new BiometricScheduler<>(
-                    new Handler(TestableLooper.get(this).getLooper()),
-                    BiometricScheduler.SENSOR_TYPE_UNKNOWN,
-                    null /* gestureAvailabilityDispatcher */,
-                    mBiometricService,
-                    LOG_NUM_RECENT_OPERATIONS,
-                    () -> mCurrentUserId,
-                    new UserSwitchProvider<IFingerprint, ISession>() {
-                        @NonNull
-                        @Override
-                        public StopUserClient<ISession> getStopUserClient(int userId) {
-                            return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
-                                    TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
-                                    mUserStoppedCallback, () -> mShouldFailStopUser);
-                        }
 
-                        @NonNull
-                        @Override
-                        public StartUserClient<IFingerprint, ISession> getStartUserClient(
-                                int newUserId) {
-                            mStartUserClientCount++;
-                            return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
-                                    newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
-                                    mUserStartedCallback, mStartOperationsFinish);
-                        }
-                    });
-        } else {
-            mScheduler = new BiometricScheduler<>(
-                    new Handler(TestableLooper.get(this).getLooper()),
-                    BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
-                    mBiometricService, LOG_NUM_RECENT_OPERATIONS);
-        }
+        mScheduler = new BiometricScheduler<>(
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_UNKNOWN,
+                null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
+                LOG_NUM_RECENT_OPERATIONS,
+                () -> mCurrentUserId,
+                new UserSwitchProvider<IFingerprint, ISession>() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
+                                TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                mUserStoppedCallback, () -> mShouldFailStopUser);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                            int newUserId) {
+                        mStartUserClientCount++;
+                        return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
+                                newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                mUserStartedCallback, mStartOperationsFinish);
+                    }
+                });
     }
 
     @Test
@@ -657,7 +655,6 @@
     @Test
     public void testClearBiometricQueue_clearsHungAuthOperation() {
         // Creating a hung client
-        final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
                 lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -676,9 +673,9 @@
         assertNotNull(mScheduler.mCurrentOperation);
         assertEquals(0, mScheduler.getCurrentPendingCount());
 
-        looper.moveTimeForward(10000);
+        mLooper.moveTimeForward(10000);
         waitForIdle();
-        looper.moveTimeForward(3000);
+        mLooper.moveTimeForward(3000);
         waitForIdle();
 
         // The hung client did not honor this operation, verify onError and authenticated
@@ -693,7 +690,6 @@
     @Test
     public void testAuthWorks_afterClearBiometricQueue() {
         // Creating a hung client
-        final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
                 lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -714,10 +710,10 @@
         waitForIdle();
 
         // The watchdog should kick off the cancellation
-        looper.moveTimeForward(10000);
+        mLooper.moveTimeForward(10000);
         waitForIdle();
         // After 10 seconds the HAL has 3 seconds to respond to a cancel
-        looper.moveTimeForward(3000);
+        mLooper.moveTimeForward(3000);
         waitForIdle();
 
         // The hung client did not honor this operation, verify onError and authenticated
@@ -752,10 +748,10 @@
         client2.getCallback().onClientFinished(client2, true);
         waitForIdle();
 
-        looper.moveTimeForward(10000);
+        mLooper.moveTimeForward(10000);
         waitForIdle();
         // After 10 seconds the HAL has 3 seconds to respond to a cancel
-        looper.moveTimeForward(3000);
+        mLooper.moveTimeForward(3000);
         waitForIdle();
 
         //Asserting auth client passes
@@ -766,7 +762,6 @@
     @Test
     public void testClearBiometricQueue_doesNotClearOperationsWhenQueueNotStuck() {
         //Creating clients
-        final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
                 lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -793,10 +788,10 @@
         waitForIdle();
 
         // The watchdog should kick off the cancellation
-        looper.moveTimeForward(10000);
+        mLooper.moveTimeForward(10000);
         waitForIdle();
         // After 10 seconds the HAL has 3 seconds to respond to a cancel
-        looper.moveTimeForward(3000);
+        mLooper.moveTimeForward(3000);
         waitForIdle();
 
         //Watchdog does not clear pending operations
@@ -855,7 +850,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenNoUser() {
         mCurrentUserId = UserHandle.USER_NULL;
 
@@ -871,7 +865,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenNoUser_notStarted() {
         mCurrentUserId = UserHandle.USER_NULL;
         mStartOperationsFinish = false;
@@ -896,7 +889,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenNoUser_notStarted_andReset() {
         mCurrentUserId = UserHandle.USER_NULL;
         mStartOperationsFinish = false;
@@ -923,7 +915,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenSameUser() {
         mCurrentUserId = 10;
 
@@ -940,7 +931,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenDifferentUser() {
         mCurrentUserId = 10;
 
@@ -961,7 +951,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testStartUser_alwaysStartsNextOperation() {
         mCurrentUserId = UserHandle.USER_NULL;
 
@@ -990,7 +979,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testStartUser_failsClearsStopUserClient() {
         mCurrentUserId = UserHandle.USER_NULL;
 
@@ -1033,7 +1021,7 @@
     }
 
     private void waitForIdle() {
-        TestableLooper.get(this).processAllMessages();
+        mLooper.dispatchAll();
     }
 
     private static class TestAuthenticateOptions implements AuthenticateOptions {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
deleted file mode 100644
index dd5d826..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static android.testing.TestableLooper.RunWithLooper;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-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.biometrics.IBiometricService;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class UserAwareBiometricSchedulerTest {
-
-    private static final String TAG = "UserAwareBiometricSchedulerTest";
-    private static final int TEST_SENSOR_ID = 0;
-
-    @Rule
-    public final MockitoRule mockito = MockitoJUnit.rule();
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    private Handler mHandler;
-    private UserAwareBiometricScheduler mScheduler;
-    private final IBinder mToken = new Binder();
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private IBiometricService mBiometricService;
-    @Mock
-    private BiometricLogger mBiometricLogger;
-    @Mock
-    private BiometricContext mBiometricContext;
-
-    private boolean mShouldFailStopUser = false;
-    private final StopUserClientShouldFail mStopUserClientShouldFail =
-            () -> {
-                return mShouldFailStopUser;
-            };
-    private final TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
-    private final TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
-    private int mCurrentUserId = UserHandle.USER_NULL;
-    private boolean mStartOperationsFinish = true;
-    private int mStartUserClientCount = 0;
-
-    @Before
-    public void setUp() {
-        mShouldFailStopUser = false;
-        mHandler = new Handler(TestableLooper.get(this).getLooper());
-        mScheduler = new UserAwareBiometricScheduler(TAG,
-                mHandler,
-                BiometricScheduler.SENSOR_TYPE_UNKNOWN,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> mCurrentUserId,
-                new UserAwareBiometricScheduler.UserSwitchCallback() {
-                    @NonNull
-                    @Override
-                    public StopUserClient<?> getStopUserClient(int userId) {
-                        return new TestStopUserClient(mContext, Object::new, mToken, userId,
-                                TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
-                                mUserStoppedCallback, mStopUserClientShouldFail);
-                    }
-
-                    @NonNull
-                    @Override
-                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
-                        mStartUserClientCount++;
-                        return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
-                                TEST_SENSOR_ID,  mBiometricLogger, mBiometricContext,
-                                mUserStartedCallback, mStartOperationsFinish);
-                    }
-                });
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void testScheduleOperation_whenNoUser() {
-        mCurrentUserId = UserHandle.USER_NULL;
-
-        final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(0);
-
-        mScheduler.scheduleClientMonitor(nextClient);
-        waitForIdle();
-
-        assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
-        assertThat(mUserStartedCallback.mStartedUsers).containsExactly(0);
-        verify(nextClient).start(any());
-    }
-
-    @Test
-    public void testScheduleOperation_whenNoUser_notStarted() {
-        mCurrentUserId = UserHandle.USER_NULL;
-        mStartOperationsFinish = false;
-
-        final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
-                mock(BaseClientMonitor.class),
-                mock(BaseClientMonitor.class),
-                mock(BaseClientMonitor.class)
-        };
-        for (BaseClientMonitor client : nextClients) {
-            when(client.getTargetUserId()).thenReturn(5);
-            mScheduler.scheduleClientMonitor(client);
-            waitForIdle();
-        }
-
-        assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
-        assertThat(mUserStartedCallback.mStartedUsers).isEmpty();
-        assertThat(mStartUserClientCount).isEqualTo(1);
-        for (BaseClientMonitor client : nextClients) {
-            verify(client, never()).start(any());
-        }
-    }
-
-    @Test
-    public void testScheduleOperation_whenNoUser_notStarted_andReset() {
-        mCurrentUserId = UserHandle.USER_NULL;
-        mStartOperationsFinish = false;
-
-        final BaseClientMonitor client = mock(BaseClientMonitor.class);
-        when(client.getTargetUserId()).thenReturn(5);
-        mScheduler.scheduleClientMonitor(client);
-        waitForIdle();
-
-        final TestStartUserClient startUserClient =
-                (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
-        mScheduler.reset();
-        assertThat(mScheduler.mCurrentOperation).isNull();
-
-        final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
-                mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
-        mScheduler.mCurrentOperation = fakeOperation;
-        startUserClient.mCallback.onClientFinished(startUserClient, true);
-        assertThat(fakeOperation).isSameInstanceAs(mScheduler.mCurrentOperation);
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void testScheduleOperation_whenSameUser() {
-        mCurrentUserId = 10;
-
-        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
-
-        mScheduler.scheduleClientMonitor(nextClient);
-
-        waitForIdle();
-
-        verify(nextClient).start(any());
-        assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
-        assertThat(mUserStartedCallback.mStartedUsers).isEmpty();
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void testScheduleOperation_whenDifferentUser() {
-        mCurrentUserId = 10;
-
-        final int nextUserId = 11;
-        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(nextUserId);
-
-        mScheduler.scheduleClientMonitor(nextClient);
-
-        waitForIdle();
-        assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(1);
-
-        waitForIdle();
-        assertThat(mUserStartedCallback.mStartedUsers).containsExactly(nextUserId);
-
-        waitForIdle();
-        verify(nextClient).start(any());
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void testStartUser_alwaysStartsNextOperation() {
-        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(10);
-
-        mScheduler.scheduleClientMonitor(nextClient);
-
-        waitForIdle();
-        verify(nextClient).start(any());
-
-        // finish first operation
-        mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
-        waitForIdle();
-
-        // schedule second operation but swap out the current operation
-        // before it runs so that it's not current when it's completion callback runs
-        nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(11);
-        mUserStartedCallback.mAfterStart = () -> mScheduler.mCurrentOperation = null;
-        mScheduler.scheduleClientMonitor(nextClient);
-
-        waitForIdle();
-        verify(nextClient).start(any());
-        assertThat(mUserStartedCallback.mStartedUsers).containsExactly(10, 11).inOrder();
-        assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(1);
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void testStartUser_failsClearsStopUserClient() {
-        // When a stop user client fails, check that mStopUserClient
-        // is set to null to prevent the scheduler from getting stuck.
-        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(10);
-
-        mScheduler.scheduleClientMonitor(nextClient);
-
-        waitForIdle();
-        verify(nextClient).start(any());
-
-        // finish first operation
-        mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
-        waitForIdle();
-
-        // schedule second operation but swap out the current operation
-        // before it runs so that it's not current when it's completion callback runs
-        nextClient = mock(BaseClientMonitor.class);
-        when(nextClient.getTargetUserId()).thenReturn(11);
-        mUserStartedCallback.mAfterStart = () -> mScheduler.mCurrentOperation = null;
-        mShouldFailStopUser = true;
-        mScheduler.scheduleClientMonitor(nextClient);
-
-        waitForIdle();
-        assertThat(mUserStartedCallback.mStartedUsers).containsExactly(10, 11).inOrder();
-        assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
-        assertThat(mScheduler.getStopUserClient()).isEqualTo(null);
-    }
-
-    private void waitForIdle() {
-        TestableLooper.get(this).processAllMessages();
-    }
-
-    private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
-        int mNumInvocations;
-
-        @Override
-        public void onUserStopped() {
-            mNumInvocations++;
-            mCurrentUserId = UserHandle.USER_NULL;
-        }
-    }
-
-    private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
-        final List<Integer> mStartedUsers = new ArrayList<>();
-        Runnable mAfterStart = null;
-
-        @Override
-        public void onUserStarted(int newUserId, Object newObject, int halInterfaceVersion) {
-            mStartedUsers.add(newUserId);
-            mCurrentUserId = newUserId;
-            if (mAfterStart != null) {
-                mAfterStart.run();
-            }
-        }
-    }
-
-    private interface StopUserClientShouldFail {
-        boolean shouldFail();
-    }
-
-    private class TestStopUserClient extends StopUserClient<Object> {
-        private StopUserClientShouldFail mShouldFailClient;
-        public TestStopUserClient(@NonNull Context context,
-                @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull BiometricLogger logger,
-                @NonNull BiometricContext biometricContext,
-                @NonNull UserStoppedCallback callback, StopUserClientShouldFail shouldFail) {
-            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
-            mShouldFailClient = shouldFail;
-        }
-
-        @Override
-        protected void startHalOperation() {
-
-        }
-
-        @Override
-        public void start(@NonNull ClientMonitorCallback callback) {
-            super.start(callback);
-            if (mShouldFailClient.shouldFail()) {
-                getCallback().onClientFinished(this, false /* success */);
-                // When the above fails, it means that the HAL has died, in this case we
-                // need to ensure the UserSwitchCallback correctly returns the NULL user handle.
-                mCurrentUserId = UserHandle.USER_NULL;
-            } else {
-                onUserStopped();
-            }
-        }
-
-        @Override
-        public void unableToStart() {
-
-        }
-    }
-
-    private static class TestStartUserClient extends StartUserClient<Object, Object> {
-        private final boolean mShouldFinish;
-
-        ClientMonitorCallback mCallback;
-
-        public TestStartUserClient(@NonNull Context context,
-                @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull BiometricLogger logger,
-                @NonNull BiometricContext biometricContext,
-                @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
-            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
-            mShouldFinish = shouldFinish;
-        }
-
-        @Override
-        protected void startHalOperation() {
-
-        }
-
-        @Override
-        public void start(@NonNull ClientMonitorCallback callback) {
-            super.start(callback);
-
-            mCallback = callback;
-            if (mShouldFinish) {
-                mUserStartedCallback.onUserStarted(
-                        getTargetUserId(), new Object(), 1 /* halInterfaceVersion */);
-                callback.onClientFinished(this, true /* success */);
-            }
-        }
-
-        @Override
-        public void unableToStart() {
-
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index e4c56a7..1ca36a3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -147,11 +147,10 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
-    public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+    public void registerAuthenticators_defaultOnly() throws Exception {
         initService();
 
-        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
@@ -161,13 +160,13 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
+    @RequiresFlagsEnabled(Flags.FLAG_FACE_VHAL_FEATURE)
     public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
         initService();
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
 
-        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -176,13 +175,13 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
-    public void registerAuthenticatorsLegacy_virtualFaceOnly() throws Exception {
+    @RequiresFlagsEnabled(Flags.FLAG_FACE_VHAL_FEATURE)
+    public void registerAuthenticators_virtualFaceOnly() throws Exception {
         initService();
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 1);
 
-        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -191,13 +190,12 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
-    public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+    public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
         mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL});
         initService();
 
-        mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+        mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -210,7 +208,7 @@
         FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
                 .build();
         initService();
-        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
         waitForRegistration();
 
         final long operationId = 5;
@@ -230,7 +228,7 @@
                 R.string.config_keyguardComponent,
                 OP_PACKAGE_NAME);
         initService();
-        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
         waitForRegistration();
         mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
                 faceAuthenticateOptions);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 84c3684..a556f52 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -50,8 +50,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -60,7 +58,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -137,6 +134,8 @@
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
     @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
+    @Captor
+    private ArgumentCaptor<FaceAuthenticateOptions> mFaceAuthenticateOptionsCaptor;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -159,21 +158,26 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void authWithContext_v2() throws RemoteException {
         final FaceAuthenticationClient client = createClient(2);
         client.start(mCallback);
 
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(),
+                mFaceAuthenticateOptionsCaptor.capture());
+
+        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor.getValue().toAidlContext(
+                mFaceAuthenticateOptionsCaptor.getValue()));
         InOrder order = inOrder(mHal, mBiometricContext);
         order.verify(mBiometricContext).updateContext(
                 mOperationContextCaptor.capture(), anyBoolean());
 
         final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+
         order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
         assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
         assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
                 .isEqualTo(AUTH_REASON);
-
         verify(mHal, never()).authenticate(anyLong());
     }
 
@@ -200,30 +204,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void notifyHalWhenContextChanges() throws RemoteException {
-        final FaceAuthenticationClient client = createClient();
-        client.start(mCallback);
-
-        final ArgumentCaptor<OperationContext> captor =
-                ArgumentCaptor.forClass(OperationContext.class);
-        verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
-        OperationContext opContext = captor.getValue();
-
-        // fake an update to the context
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
-        assertThat(opContext).isSameInstanceAs(
-                mOperationContextCaptor.getValue().toAidlContext());
-        mContextInjector.getValue().accept(opContext);
-        verify(mHal).onContextChanged(same(opContext));
-
-        client.stopHalOperation();
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void subscribeContextAndStartHal() throws RemoteException {
         final FaceAuthenticationClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index e626f73..fd3f054 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -20,7 +20,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
@@ -37,8 +36,6 @@
 import android.os.RemoteException;
 import android.os.Vibrator;
 import android.platform.test.annotations.Presubmit;
-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.testing.TestableContext;
@@ -46,7 +43,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -58,7 +54,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -124,49 +119,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void detectWithContext_v2() throws RemoteException {
-        final FaceDetectClient client = createClient(2);
-        client.start(mCallback);
-
-        InOrder order = inOrder(mHal, mBiometricContext);
-        order.verify(mBiometricContext).updateContext(
-                mOperationContextCaptor.capture(), anyBoolean());
-
-        final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
-        order.verify(mHal).detectInteractionWithContext(same(aidlContext));
-        assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
-        assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
-                .isEqualTo(AUTH_REASON);
-
-        verify(mHal, never()).detectInteraction();
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void notifyHalWhenContextChanges() throws RemoteException {
-        final FaceDetectClient client = createClient();
-        client.start(mCallback);
-
-        final ArgumentCaptor<OperationContext> captor =
-                ArgumentCaptor.forClass(OperationContext.class);
-        verify(mHal).detectInteractionWithContext(captor.capture());
-        OperationContext opContext = captor.getValue();
-
-        // fake an update to the context
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
-        assertThat(opContext).isSameInstanceAs(
-                mOperationContextCaptor.getValue().toAidlContext());
-        mContextInjector.getValue().accept(opContext);
-        verify(mHal).onContextChanged(same(opContext));
-
-        client.stopHalOperation();
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void subscribeContextAndStartHal() throws RemoteException {
         final FaceDetectClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 02363cd..d6b5789 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
@@ -38,8 +37,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-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.testing.TestableContext;
@@ -47,7 +44,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -60,7 +56,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -124,45 +119,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void enrollWithContext_v2() throws RemoteException {
-        final FaceEnrollClient client = createClient(2);
-        client.start(mCallback);
-
-        InOrder order = inOrder(mHal, mBiometricContext);
-        order.verify(mBiometricContext).updateContext(
-                mOperationContextCaptor.capture(), anyBoolean());
-
-        final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
-        order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), same(aidlContext));
-        verify(mHal, never()).enroll(any(), anyByte(), any(), any());
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void notifyHalWhenContextChanges() throws RemoteException {
-        final FaceEnrollClient client = createClient(3);
-        client.start(mCallback);
-
-        final ArgumentCaptor<OperationContext> captor =
-                ArgumentCaptor.forClass(OperationContext.class);
-        verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), captor.capture());
-        OperationContext opContext = captor.getValue();
-
-        // fake an update to the context
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
-        assertThat(opContext).isSameInstanceAs(
-                mOperationContextCaptor.getValue().toAidlContext());
-        mContextInjector.getValue().accept(opContext);
-        verify(mHal).onContextChanged(same(opContext));
-
-        client.stopHalOperation();
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void subscribeContextAndStartHal() throws RemoteException {
         final FaceEnrollClient client = createClient(3);
         client.start(mCallback);
@@ -192,16 +148,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void enrollWithFaceOptions() throws RemoteException {
-        final FaceEnrollClient client = createClient(4);
-        client.start(mCallback);
-
-        verify(mHal).enrollWithOptions(any());
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void enrollWithFaceOptionsAfterSubscribingContext() throws RemoteException {
         final FaceEnrollClient client = createClient(4);
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 9eca93e..c4e51f8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -44,22 +44,18 @@
 import android.hardware.face.HidlFaceSensorConfig;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -134,13 +130,8 @@
         when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
                 mBiometricCallbackHandler);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
-        if (Flags.deHidl()) {
-            when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
-                    mLooper.getLooper()));
-        } else {
-            when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
-                    Looper.getMainLooper()));
-        }
+        when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+                mLooper.getLooper()));
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
@@ -176,7 +167,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testAddingHidlSensors() {
         when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
         when(mResources.getBoolean(anyInt())).thenReturn(false);
@@ -247,7 +237,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testAuthenticateCallbackHandler() {
         waitForIdle();
 
@@ -295,10 +284,6 @@
     }
 
     private void waitForIdle() {
-        if (Flags.deHidl()) {
-            mLooper.dispatchAll();
-        } else {
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        }
+        mLooper.dispatchAll();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index fe9cd43..6780e60 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -41,7 +41,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -50,7 +49,6 @@
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.UserSwitchProvider;
 
 import org.junit.Before;
@@ -75,12 +73,8 @@
     @Mock
     private ISession mSession;
     @Mock
-    private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
-    @Mock
     private UserSwitchProvider<IFace, ISession> mUserSwitchProvider;
     @Mock
-    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
-    @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
     @Mock
     private BiometricLogger mBiometricLogger;
@@ -94,6 +88,8 @@
     private BaseClientMonitor mClientMonitor;
     @Mock
     private AidlSession mCurrentSession;
+    @Mock
+    private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -108,27 +104,17 @@
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
 
-        if (Flags.deHidl()) {
-            mScheduler = new BiometricScheduler<>(
-                    new Handler(mLooper.getLooper()),
-                    BiometricScheduler.SENSOR_TYPE_FACE,
-                    null /* gestureAvailabilityDispatcher */,
-                    mBiometricService,
-                    2 /* recentOperationsLimit */,
-                    () -> USER_ID,
-                    mUserSwitchProvider);
-        } else {
-            mScheduler = new UserAwareBiometricScheduler<>(TAG,
-                    new Handler(mLooper.getLooper()),
-                    BiometricScheduler.SENSOR_TYPE_FACE,
-                    null /* gestureAvailabilityDispatcher */,
-                    mBiometricService,
-                    () -> USER_ID,
-                    mUserSwitchCallback);
-        }
+        mScheduler = new BiometricScheduler<>(
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
+                2 /* recentOperationsLimit */,
+                () -> USER_ID,
+                mUserSwitchProvider);
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mHardwareUnavailableCallback);
+                mAidlResponseHandlerCallback);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
deleted file mode 100644
index 949d6ee..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.IntStream;
-
-@Presubmit
-@SmallTest
-public class Face10Test {
-
-    private static final String TAG = "Face10Test";
-    private static final int SENSOR_ID = 1;
-    private static final int USER_ID = 20;
-    private static final float FRR_THRESHOLD = 0.2f;
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private BiometricScheduler mScheduler;
-    @Mock
-    private BiometricContext mBiometricContext;
-    @Mock
-    private BiometricStateCallback mBiometricStateCallback;
-    @Mock
-    private AuthenticationStateListeners mAuthenticationStateListeners;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private LockoutResetDispatcher mLockoutResetDispatcher;
-    private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
-    private IBinder mBinder;
-
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-        when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
-
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
-                .thenReturn(FRR_THRESHOLD);
-
-        mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-
-        final int maxEnrollmentsPerUser = 1;
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        final boolean supportsFaceDetection = false;
-        final boolean supportsSelfIllumination = false;
-        final boolean resetLockoutRequiresChallenge = false;
-        final FaceSensorPropertiesInternal sensorProps = new FaceSensorPropertiesInternal(SENSOR_ID,
-                SensorProperties.STRENGTH_STRONG, maxEnrollmentsPerUser, componentInfo,
-                FaceSensorProperties.TYPE_UNKNOWN, supportsFaceDetection, supportsSelfIllumination,
-                resetLockoutRequiresChallenge);
-
-        Face10.sSystemClock = Clock.fixed(
-                Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
-        mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
-                sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
-        mBinder = new Binder();
-    }
-
-    private void tick(long seconds) {
-        waitForIdle();
-        Face10.sSystemClock = Clock.fixed(Instant.ofEpochSecond(
-                Face10.sSystemClock.instant().getEpochSecond() + seconds),
-                ZoneId.of("America/Los_Angeles"));
-    }
-
-    @Test
-    public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
-        assertEquals(0, mFace10.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
-        waitForIdle();
-    }
-
-    @Test
-    public void scheduleRevokeChallenge_doesNotCrash() {
-        mFace10.scheduleRevokeChallenge(0 /* sensorId */, 0 /* userId */, mBinder, TAG,
-                0 /* challenge */);
-        waitForIdle();
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void scheduleGenerateChallenge_cachesResult() {
-        final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
-                .mapToObj(i -> mock(IFaceServiceReceiver.class))
-                .toArray(IFaceServiceReceiver[]::new);
-        for (IFaceServiceReceiver mock : mocks) {
-            mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
-            tick(10);
-        }
-        tick(120);
-        mFace10.scheduleGenerateChallenge(
-                SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
-        waitForIdle();
-
-        verify(mScheduler, times(2))
-                .scheduleClientMonitor(isA(FaceGenerateChallengeClient.class), any());
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void scheduleRevokeChallenge_waitsUntilEmpty() {
-        final long challenge = 22;
-        final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
-                .mapToObj(i -> mock(IFaceServiceReceiver.class))
-                .toArray(IFaceServiceReceiver[]::new);
-        for (IFaceServiceReceiver mock : mocks) {
-            mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
-            tick(10);
-        }
-        for (IFaceServiceReceiver mock : mocks) {
-            mFace10.scheduleRevokeChallenge(SENSOR_ID, USER_ID, mBinder, TAG, challenge);
-            tick(10);
-        }
-        waitForIdle();
-
-        verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void scheduleRevokeChallenge_doesNotWaitForever() {
-        mFace10.scheduleGenerateChallenge(
-                SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
-        mFace10.scheduleGenerateChallenge(
-                SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
-        tick(10000);
-        mFace10.scheduleGenerateChallenge(
-                SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
-        mFace10.scheduleRevokeChallenge(
-                SENSOR_ID, USER_ID, mBinder, TAG, 8 /* challenge */);
-        waitForIdle();
-
-        verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
-    }
-
-    @Test
-    public void halServiceDied_resetsScheduler() {
-        // It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
-        // serviceDied directly.
-        mFace10.serviceDied(0 /* cookie */);
-        waitForIdle();
-        verify(mScheduler).reset();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
deleted file mode 100644
index ec08329..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.face.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-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.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.OptionalUint64;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Presubmit
-@SmallTest
-public class FaceGenerateChallengeClientTest {
-
-    private static final String TAG = "FaceGenerateChallengeClientTest";
-    private static final int USER_ID = 2;
-    private static final int SENSOR_ID = 4;
-    private static final long START_TIME = 5000;
-    private static final long CHALLENGE = 200;
-
-    private final Context mContext = ApplicationProvider.getApplicationContext();
-
-    @Mock
-    private IBiometricsFace mIBiometricsFace;
-    @Mock
-    private IFaceServiceReceiver mClientReceiver;
-    @Mock
-    private IFaceServiceReceiver mOtherReceiver;
-    @Mock
-    private ClientMonitorCallback mMonitorCallback;
-    @Mock
-    private BiometricLogger mBiometricLogger;
-    @Mock
-    private BiometricContext mBiometricContext;
-
-    private FaceGenerateChallengeClient mClient;
-
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        final OptionalUint64 challenge = new OptionalUint64();
-        challenge.value = CHALLENGE;
-        when(mIBiometricsFace.generateChallenge(anyInt())).thenReturn(challenge);
-
-        mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
-                new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
-                TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
-    }
-
-    @Test
-    public void getCreatedAt() {
-        assertEquals(START_TIME, mClient.getCreatedAt());
-    }
-
-    @Test
-    public void reuseResult_whenNotReady() throws Exception {
-        mClient.reuseResult(mOtherReceiver);
-        verify(mOtherReceiver, never()).onChallengeGenerated(anyInt(), anyInt(), anyInt());
-    }
-
-    @Test
-    public void reuseResult_whenReady() throws Exception {
-        mClient.start(mMonitorCallback);
-        mClient.reuseResult(mOtherReceiver);
-        verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
-    }
-
-    @Test
-    public void reuseResult_whenReallyReady() throws Exception {
-        mClient.reuseResult(mOtherReceiver);
-        mClient.start(mMonitorCallback);
-        verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index b5d73d2..44da431 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
-import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
 import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.CHALLENGE_TIMEOUT_SEC;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 9a8cd48..6126af5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -51,7 +51,6 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
@@ -64,7 +63,6 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -90,8 +88,6 @@
     private static final int ID_VIRTUAL = 6;
     private static final String NAME_DEFAULT = "default";
     private static final String NAME_VIRTUAL = "virtual";
-    private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
-            List.of();
     private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
 
     @Rule
@@ -185,7 +181,7 @@
 
     private void initServiceWithAndWait(String... aidlInstances) throws Exception {
         initServiceWith(aidlInstances);
-        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+        mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
         waitForRegistration();
     }
 
@@ -193,18 +189,7 @@
     public void registerAuthenticators_defaultOnly() throws Exception {
         initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
 
-        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
-        waitForRegistration();
-
-        verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
-    public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
-        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
-
-        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
@@ -216,7 +201,7 @@
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
 
-        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+        mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -228,20 +213,7 @@
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 1);
 
-        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
-        waitForRegistration();
-
-        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
-    public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
-        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
-        Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
-                Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
-
-        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -249,23 +221,12 @@
 
     @Test
     public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
-        initServiceWith(NAME_VIRTUAL);
-
-        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
-        waitForRegistration();
-
-        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
-    public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
         mFingerprintSensorConfigurations =
                 new FingerprintSensorConfigurations(true);
         mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL});
         initServiceWith(NAME_VIRTUAL);
 
-        mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+        mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 7a77392..db9fe7f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -30,7 +30,6 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.same;
@@ -42,7 +41,6 @@
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.ISession;
@@ -52,13 +50,10 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -67,7 +62,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -84,7 +78,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -180,36 +173,18 @@
     public void authNoContext_v1() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
-        if (Flags.deHidl()) {
-            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
-                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
-            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
-                    .getValue().toAidlContext());
-        }
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                .getValue().toAidlContext());
 
         verify(mHal).authenticate(eq(OP_ID));
         verify(mHal, never()).authenticateWithContext(anyLong(), any());
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void authWithContext_v2() throws RemoteException {
-        final FingerprintAuthenticationClient client = createClient(2);
-        client.start(mCallback);
-
-        InOrder order = inOrder(mHal, mBiometricContext);
-        order.verify(mBiometricContext).updateContext(
-                mOperationContextCaptor.capture(), anyBoolean());
-
-        final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
-        order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
-        assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
-                .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
-
-        verify(mHal, never()).authenticate(anyLong());
-    }
-
-    @Test
     public void pointerUp_v1() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
@@ -277,21 +252,18 @@
 
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
-        if (Flags.deHidl()) {
-            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
-                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
-            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
-                    .getValue().toAidlContext());
-        }
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                .getValue().toAidlContext());
 
         final ArgumentCaptor<OperationContext> captor =
                 ArgumentCaptor.forClass(OperationContext.class);
         verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
         OperationContext opContext = captor.getValue();
-        if (!Flags.deHidl()) {
-            verify(mBiometricContext).subscribe(
-                    mOperationContextCaptor.capture(), mContextInjector.capture());
-        }
+
         assertThat(mOperationContextCaptor.getValue().toAidlContext())
                 .isSameInstanceAs(opContext);
 
@@ -326,12 +298,12 @@
         when(mBiometricContext.isAod()).thenReturn(false);
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
-        if (Flags.deHidl()) {
-            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
-                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
-            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
-                    .getValue().toAidlContext());
-        }
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                .getValue().toAidlContext());
 
         verify(mLuxProbe, isAwake ? times(1) : never()).enable();
     }
@@ -342,21 +314,18 @@
         when(mBiometricContext.isAod()).thenReturn(true);
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
-        if (Flags.deHidl()) {
-            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
-                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
-            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
-                    .getValue().toAidlContext());
-        }
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                .getValue().toAidlContext());
 
         final ArgumentCaptor<OperationContext> captor =
                 ArgumentCaptor.forClass(OperationContext.class);
         verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
         OperationContext opContext = captor.getValue();
-        if (!Flags.deHidl()) {
-            verify(mBiometricContext).subscribe(
-                    mOperationContextCaptor.capture(), mContextInjector.capture());
-        }
+
         assertThat(opContext).isSameInstanceAs(
                 mOperationContextCaptor.getValue().toAidlContext());
 
@@ -380,30 +349,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void notifyHalWhenContextChanges() throws RemoteException {
-        final FingerprintAuthenticationClient client = createClient();
-        client.start(mCallback);
-
-        final ArgumentCaptor<OperationContext> captor =
-                ArgumentCaptor.forClass(OperationContext.class);
-        verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
-        OperationContext opContext = captor.getValue();
-
-        // fake an update to the context
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
-        assertThat(opContext).isSameInstanceAs(
-                mOperationContextCaptor.getValue().toAidlContext());
-        mContextInjector.getValue().accept(opContext);
-        verify(mHal).onContextChanged(same(opContext));
-
-        client.stopHalOperation();
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void subscribeContextAndStartHal() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
@@ -603,7 +548,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testLockoutTracker_authFailed() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(1 /* version */,
                 true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
@@ -658,8 +602,7 @@
                 null /* taskStackListener */,
                 mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners,
                 allowBackgroundAuthentication,
-                mSensorProps,
-                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+                mSensorProps, 0 /* biometricStrength */,
                 lockoutTracker) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 9edb8dd..6b8c3cd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -21,13 +21,11 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -35,8 +33,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-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.testing.TestableContext;
@@ -44,7 +40,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -56,7 +51,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -119,49 +113,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void detectNoContext_v2() throws RemoteException {
-        final FingerprintDetectClient client = createClient(2);
-
-        client.start(mCallback);
-
-        InOrder order = inOrder(mHal, mBiometricContext);
-        order.verify(mBiometricContext).updateContext(
-                mOperationContextCaptor.capture(), anyBoolean());
-
-        final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
-        order.verify(mHal).detectInteractionWithContext(same(aidlContext));
-        assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
-                .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
-
-        verify(mHal, never()).detectInteraction();
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void notifyHalWhenContextChanges() throws RemoteException {
-        final FingerprintDetectClient client = createClient();
-        client.start(mCallback);
-
-        final ArgumentCaptor<OperationContext> captor =
-                ArgumentCaptor.forClass(OperationContext.class);
-        verify(mHal).detectInteractionWithContext(captor.capture());
-        OperationContext opContext = captor.getValue();
-
-        // fake an update to the context
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
-        assertThat(opContext).isSameInstanceAs(
-                mOperationContextCaptor.getValue().toAidlContext());
-        mContextInjector.getValue().accept(opContext);
-        verify(mHal).onContextChanged(same(opContext));
-
-        client.stopHalOperation();
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void subscribeContextAndStartHal() throws RemoteException {
         final FingerprintDetectClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 916f696..d2e1c3c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.times;
@@ -44,8 +43,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -54,7 +51,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -70,7 +66,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -156,21 +151,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void enrollWithContext_v2() throws RemoteException {
-        final FingerprintEnrollClient client = createClient(2);
-
-        client.start(mCallback);
-
-        InOrder order = inOrder(mHal, mBiometricContext);
-        order.verify(mBiometricContext).updateContext(
-                mOperationContextCaptor.capture(), anyBoolean());
-        order.verify(mHal).enrollWithContext(any(),
-                same(mOperationContextCaptor.getValue().toAidlContext()));
-        verify(mHal, never()).enroll(any());
-    }
-
-    @Test
     public void pointerUp_v1() throws RemoteException {
         final FingerprintEnrollClient client = createClient(1);
         client.start(mCallback);
@@ -253,29 +233,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
-    public void notifyHalWhenContextChanges() throws RemoteException {
-        final FingerprintEnrollClient client = createClient();
-        client.start(mCallback);
-
-        final ArgumentCaptor<OperationContext> captor =
-                ArgumentCaptor.forClass(OperationContext.class);
-        verify(mHal).enrollWithContext(any(), captor.capture());
-        OperationContext opContext = captor.getValue();
-
-        // fake an update to the context
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
-        mContextInjector.getValue().accept(
-                mOperationContextCaptor.getValue().toAidlContext());
-        verify(mHal).onContextChanged(same(opContext));
-
-        client.stopHalOperation();
-        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void subscribeContextAndStartHal() throws RemoteException {
         final FingerprintEnrollClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 0a35037..1f288b2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -47,21 +47,17 @@
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -136,13 +132,8 @@
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
         when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
                 mBiometricCallbackHandler);
-        if (Flags.deHidl()) {
-            when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
-                    new Handler(mLooper.getLooper()));
-        } else {
-            when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
-                    new Handler(Looper.getMainLooper()));
-        }
+        when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+                new Handler(mLooper.getLooper()));
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
@@ -176,7 +167,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testAddingHidlSensors() {
         when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
         when(mResources.getBoolean(anyInt())).thenReturn(false);
@@ -252,7 +242,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void testScheduleAuthenticate() {
         waitForIdle();
 
@@ -302,10 +291,6 @@
     }
 
     private void waitForIdle() {
-        if (Flags.deHidl()) {
-            mLooper.dispatchAll();
-        } else {
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        }
+        mLooper.dispatchAll();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index b4c2ee8..698db2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -42,7 +42,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -51,7 +50,6 @@
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
@@ -77,12 +75,8 @@
     @Mock
     private ISession mSession;
     @Mock
-    private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
-    @Mock
     private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider;
     @Mock
-    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
-    @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
     @Mock
     private BiometricLogger mLogger;
@@ -100,6 +94,8 @@
     private BaseClientMonitor mClientMonitor;
     @Mock
     private HandlerThread mThread;
+    @Mock
+    AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -115,27 +111,17 @@
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
         when(mThread.getLooper()).thenReturn(mLooper.getLooper());
 
-        if (Flags.deHidl()) {
-            mScheduler = new BiometricScheduler<>(
-                    new Handler(mLooper.getLooper()),
-                    BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                    null /* gestureAvailabilityDispatcher */,
-                    mBiometricService,
-                    2 /* recentOperationsLimit */,
-                    () -> USER_ID,
-                    mUserSwitchProvider);
-        } else {
-            mScheduler = new UserAwareBiometricScheduler<>(TAG,
-                    new Handler(mLooper.getLooper()),
-                    BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                    null /* gestureAvailabilityDispatcher */,
-                    mBiometricService,
-                    () -> USER_ID,
-                    mUserSwitchCallback);
-        }
+        mScheduler = new BiometricScheduler<>(
+                new Handler(mLooper.getLooper()),
+                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
+                2 /* recentOperationsLimit */,
+                () -> USER_ID,
+                mUserSwitchProvider);
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mHardwareUnavailableCallback);
+                mAidlResponseHandlerCallback);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
deleted file mode 100644
index 0d3f192..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class Fingerprint21Test {
-
-    private static final String TAG = "Fingerprint21Test";
-    private static final int SENSOR_ID = 1;
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    Fingerprint21.HalResultController mHalResultController;
-    @Mock
-    private BiometricScheduler mScheduler;
-    @Mock
-    private AuthenticationStateListeners mAuthenticationStateListeners;
-    @Mock
-    private BiometricStateCallback mBiometricStateCallback;
-    @Mock
-    private BiometricContext mBiometricContext;
-
-    private LockoutResetDispatcher mLockoutResetDispatcher;
-    private Fingerprint21 mFingerprint21;
-
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-        when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getInteger(eq(R.integer.config_fingerprintMaxTemplatesPerUser)))
-                .thenReturn(5);
-
-        mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-
-        final int maxEnrollmentsPerUser = 1;
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        final boolean resetLockoutRequiresHardwareAuthToken = false;
-        final FingerprintSensorPropertiesInternal sensorProps =
-                new FingerprintSensorPropertiesInternal(SENSOR_ID,
-                        FingerprintSensorProperties.STRENGTH_WEAK, maxEnrollmentsPerUser,
-                        componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
-                        resetLockoutRequiresHardwareAuthToken);
-
-        mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback,
-                mAuthenticationStateListeners, sensorProps, mScheduler,
-                new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController,
-                mBiometricContext);
-    }
-
-    @Test
-    public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
-        assertEquals(0, mFingerprint21.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
-        waitForIdle();
-    }
-
-    @Test
-    public void halServiceDied_resetsScheduler() {
-        // It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
-        // serviceDied directly.
-        mFingerprint21.serviceDied(0 /* cookie */);
-        waitForIdle();
-        verify(mScheduler).reset();
-    }
-
-    private static class TestableFingerprint21 extends Fingerprint21 {
-
-        TestableFingerprint21(@NonNull Context context,
-                @NonNull BiometricStateCallback biometricStateCallback,
-                @NonNull AuthenticationStateListeners authenticationStateListeners,
-                @NonNull FingerprintSensorPropertiesInternal sensorProps,
-                @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull HalResultController controller,
-                @NonNull BiometricContext biometricContext) {
-            super(context, biometricStateCallback, authenticationStateListeners, sensorProps,
-                    scheduler, handler, lockoutResetDispatcher, controller, biometricContext);
-        }
-
-        @Override
-        synchronized IBiometricsFingerprint getDaemon() {
-            return mock(IBiometricsFingerprint.class);
-        }
-    }
-}
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 74e854e4..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,10 +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(anyString(), anyString());
-        doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
-                mIInputManagerMock).removeUniqueIdAssociation(anyString());
+        doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
+                inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
+                        anyString(), 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.
@@ -112,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/Android.bp b/services/tests/uiservicestests/Android.bp
index 515898a..e6cf0c38 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -50,6 +50,7 @@
         "SettingsLib",
         "libprotobuf-java-lite",
         "platformprotoslite",
+        "platform-parametric-runner-lib",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index ad25d76..770712a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -26,14 +26,18 @@
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 import static android.media.AudioAttributes.USAGE_UNKNOWN;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.notification.NotificationChannelExtractor.RESTRICT_AUDIO_ATTRIBUTES;
 import static com.google.common.truth.Truth.assertThat;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Flags;
@@ -43,12 +47,14 @@
 import android.app.Person;
 import android.media.AudioAttributes;
 import android.net.Uri;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 
+import com.android.internal.compat.IPlatformCompat;
 import com.android.server.UiServiceTestCase;
 
 import org.junit.Before;
@@ -60,6 +66,8 @@
 public class NotificationChannelExtractorTest extends UiServiceTestCase {
 
     @Mock RankingConfig mConfig;
+    @Mock
+    IPlatformCompat mPlatformCompat;
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -73,6 +81,7 @@
         mExtractor = new NotificationChannelExtractor();
         mExtractor.setConfig(mConfig);
         mExtractor.initialize(mContext, null);
+        mExtractor.setCompatChangeLogger(mPlatformCompat);
     }
 
     private NotificationRecord getRecord(NotificationChannel channel, Notification n) {
@@ -82,7 +91,7 @@
     }
 
     @Test
-    public void testExtractsUpdatedConversationChannel() {
+    public void testExtractsUpdatedConversationChannel() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
         final Notification n = new Notification.Builder(getContext())
                 .setContentTitle("foo")
@@ -101,7 +110,7 @@
     }
 
     @Test
-    public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() {
+    public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
         final Notification n = new Notification.Builder(getContext())
                 .setContentTitle("foo")
@@ -122,7 +131,7 @@
     }
 
     @Test
-    public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() {
+    public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
         final Notification n = new Notification.Builder(getContext())
                 .setContentTitle("foo")
@@ -143,7 +152,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL)
-    public void testAudioAttributes_callStyleCanUseCallUsage() {
+    public void testAudioAttributes_callStyleCanUseCallUsage() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
                 .setUsage(USAGE_NOTIFICATION_RINGTONE)
@@ -162,11 +171,12 @@
         assertThat(mExtractor.process(r)).isNull();
         assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE);
         assertThat(r.getChannel()).isEqualTo(channel);
+        verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt());
     }
 
     @Test
     @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL)
-    public void testAudioAttributes_nonCallStyleCannotUseCallUsage() {
+    public void testAudioAttributes_nonCallStyleCannotUseCallUsage() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
                 .setUsage(USAGE_NOTIFICATION_RINGTONE)
@@ -180,13 +190,14 @@
         assertThat(mExtractor.process(r)).isNull();
         // instance updated
         assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+        verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
         // in-memory channel unchanged
         assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM)
-    public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() {
+    public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
                 .setUsage(USAGE_ALARM)
@@ -201,11 +212,12 @@
         assertThat(mExtractor.process(r)).isNull();
         assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
         assertThat(r.getChannel()).isEqualTo(channel);
+        verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt());
     }
 
     @Test
     @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM)
-    public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() {
+    public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
                 .setUsage(USAGE_ALARM)
@@ -219,13 +231,14 @@
         assertThat(mExtractor.process(r)).isNull();
         // instance updated
         assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+        verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
         // in-memory channel unchanged
         assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA)
-    public void testAudioAttributes_noMediaUsage() {
+    public void testAudioAttributes_noMediaUsage() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
                 .setUsage(USAGE_MEDIA)
@@ -239,13 +252,14 @@
         assertThat(mExtractor.process(r)).isNull();
         // instance updated
         assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+        verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
         // in-memory channel unchanged
         assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_MEDIA);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA)
-    public void testAudioAttributes_noUnknownUsage() {
+    public void testAudioAttributes_noUnknownUsage() throws RemoteException {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
                 .setUsage(USAGE_UNKNOWN)
@@ -259,6 +273,7 @@
         assertThat(mExtractor.process(r)).isNull();
         // instance updated
         assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+        verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
         // in-memory channel unchanged
         assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_UNKNOWN);
     }
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 4a61d32..011f2e3 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -106,22 +106,23 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
 import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
 import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
+import static com.android.server.notification.NotificationManagerService.TAG;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
-
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
-
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -130,7 +131,6 @@
 import static junit.framework.Assert.assertSame;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
-
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -138,10 +138,24 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 import android.Manifest;
 import android.annotation.Nullable;
@@ -168,6 +182,8 @@
 import android.app.RemoteInputHistoryItem;
 import android.app.StatsManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.job.JobScheduler;
+import android.app.role.RoleManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
@@ -184,9 +200,11 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
@@ -217,6 +235,7 @@
 import android.permission.PermissionManager;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.rule.LimitDevicesRule;
 import android.provider.DeviceConfig;
@@ -235,7 +254,8 @@
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 import android.telecom.TelecomManager;
-import android.testing.AndroidTestingRunner;
+import android.testing.TestWithLooperRule;
+import android.testing.TestableContentResolver;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestablePermissions;
@@ -245,13 +265,13 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Xml;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.RemoteViews;
-
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-
 import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.config.sysui.TestableFlagResolver;
@@ -259,6 +279,7 @@
 import com.android.internal.logging.InstanceIdSequenceFake;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.DeviceIdleInternal;
@@ -282,13 +303,10 @@
 import com.android.server.utils.quota.MultiRateLimiter;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
-
 import com.google.android.collect.Lists;
 import com.google.common.collect.ImmutableList;
-
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -306,6 +324,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -322,9 +342,9 @@
 import java.util.function.Consumer;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
-@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+@RunWith(ParameterizedAndroidJunit4.class)
 @RunWithLooper
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
 public class NotificationManagerServiceTest extends UiServiceTestCase {
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
     private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -369,6 +389,8 @@
     @Mock
     private PermissionHelper mPermissionHelper;
     private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
+    @Rule(order = Integer.MAX_VALUE)
+    public TestWithLooperRule mlooperRule = new TestWithLooperRule();
     private TestableLooper mTestableLooper;
     @Mock
     private RankingHelper mRankingHelper;
@@ -415,8 +437,8 @@
     private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
             new TestPostNotificationTrackerFactory();
 
-    @Mock
-    IIntentSender pi1;
+    private PendingIntent mActivityIntent;
+    private PendingIntent mActivityIntentImmutable;
 
     private static final int MAX_POST_DELAY = 1000;
 
@@ -429,6 +451,9 @@
 
     private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
     private static final String SEARCH_SELECTOR_PKG = "searchSelector";
+    private static final String ADSERVICES_MODULE_PKG = "com.android.adservices";
+    private static final String ADSERVICES_APK_PKG = "com.android.adservices.api";
+
     @Mock
     private NotificationListeners mListeners;
     @Mock
@@ -465,6 +490,7 @@
     StatsManager mStatsManager;
     @Mock
     AlarmManager mAlarmManager;
+    @Mock JobScheduler mJobScheduler;
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
@@ -508,6 +534,16 @@
         }
     }
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                FLAG_ALL_NOTIFS_NEED_TTL);
+    }
+
+    public NotificationManagerServiceTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         // Shell permisssions will override permissions of our app, so add all necessary permissions
@@ -540,12 +576,22 @@
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
         LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
         LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+        LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
         mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
         mContext.addMockSystemService(NotificationManager.class, mMockNm);
+        mContext.addMockSystemService(RoleManager.class, mock(RoleManager.class));
+        mContext.addMockSystemService(Context.LAUNCHER_APPS_SERVICE, mLauncherApps);
+        mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE,
+                mock(AccessibilityManager.class));
 
         doNothing().when(mContext).sendBroadcast(any(), anyString());
         doNothing().when(mContext).sendBroadcastAsUser(any(), any());
         doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+        TestableContentResolver cr = mock(TestableContentResolver.class);
+        when(mContext.getContentResolver()).thenReturn(cr);
+        doNothing().when(cr).registerContentObserver(any(), anyBoolean(), any(), anyInt());
 
         setDpmAppOppsExemptFromDismissal(false);
 
@@ -648,11 +694,16 @@
                 });
 
         // TODO (b/291907312): remove feature flag
-        // NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
+        // NOTE: Prefer using the @EnableFlags annotation where possible. Do not add any android.app
         //  flags here.
         mSetFlagsRule.disableFlags(
                 Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
 
+        mActivityIntent = spy(PendingIntent.getActivity(mContext, 0,
+                new Intent().setPackage(mPkg), PendingIntent.FLAG_MUTABLE));
+        mActivityIntentImmutable = spy(PendingIntent.getActivity(mContext, 0,
+                new Intent().setPackage(mPkg), FLAG_IMMUTABLE));
+
         initNMS();
     }
 
@@ -689,10 +740,15 @@
                 mPowerManager, mPostNotificationTrackerFactory);
 
         mService.setAttentionHelper(mAttentionHelper);
+        mService.setLockPatternUtils(mock(LockPatternUtils.class));
 
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
-
+        ModuleInfo moduleInfo = new ModuleInfo();
+        moduleInfo.setApexModuleName(ADSERVICES_MODULE_PKG);
+        moduleInfo.setApkInApexPackageNames(List.of(ADSERVICES_APK_PKG));
+        when(mPackageManagerClient.getInstalledModules(anyInt()))
+                .thenReturn(List.of(moduleInfo));
         if (upToBootPhase >= SystemService.PHASE_SYSTEM_SERVICES_READY) {
             mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
         }
@@ -749,7 +805,10 @@
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
         assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
-        assertNotNull("Notification timeout receiver should exist", mNotificationTimeoutReceiver);
+        if (!Flags.allNotifsNeedTtl()) {
+            assertNotNull("Notification timeout receiver should exist",
+                    mNotificationTimeoutReceiver);
+        }
 
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -834,9 +893,17 @@
         if (mFile != null) mFile.delete();
         clearDeviceConfig();
 
+        if (mActivityIntent != null) {
+            mActivityIntent.cancel();
+        }
+
+        mService.clearNotifications();
+        TestableLooper.get(this).processAllMessages();
+
         try {
             mService.onDestroy();
         } catch (IllegalStateException | IllegalArgumentException e) {
+            Log.e(TAG, "failed to destroy", e);
             // can throw if a broadcast receiver was never registered
         }
 
@@ -846,6 +913,11 @@
         // could cause issues, for example, messages that remove/cancel shown toasts (this causes
         // problematic interactions with mocks when they're no longer working as expected).
         mWorkerHandler.removeCallbacksAndMessages(null);
+
+        if (TestableLooper.get(this) != null) {
+            // Must remove static reference to this test object to prevent leak (b/261039202)
+            TestableLooper.remove(this);
+        }
     }
 
     private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
@@ -1005,7 +1077,9 @@
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .addAction(new Notification.Action.Builder(null, "test", null).build());
+                .addAction(new Notification.Action.Builder(null, "test", mActivityIntent).build())
+                .addAction(new Notification.Action.Builder(
+                        null, "test", mActivityIntentImmutable).build());
         if (extender != null) {
             nb.extend(extender);
         }
@@ -1045,18 +1119,20 @@
 
     private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
             String tag) {
-        return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+        return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false, true);
     }
 
     private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
-            NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+            NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary,
+            boolean mutable) {
         if (channel == null) {
             channel = mTestNotificationChannel;
         }
         if (tag == null) {
             tag = "tag";
         }
-        Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+        Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey,
+                isSummary, mutable);
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id,
                 tag, mUid, 0,
                 nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -1129,18 +1205,15 @@
     }
 
     private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
-            String groupKey, boolean isSummary) {
+            String groupKey, boolean isSummary, boolean mutable) {
         // Give it a person
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent().setPackage(mContext.getPackageName()),
-                PendingIntent.FLAG_MUTABLE);
         Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
+                mutable ? mActivityIntent : mActivityIntentImmutable).addRemoteInput(remoteInput)
                 .build();
         // Make it messaging style
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -1167,17 +1240,14 @@
     }
 
     private Notification.BubbleMetadata getBubbleMetadata() {
-        PendingIntent pendingIntent = mock(PendingIntent.class);
-        Intent intent = mock(Intent.class);
-        when(pendingIntent.getIntent()).thenReturn(intent);
-        when(pendingIntent.getTarget()).thenReturn(pi1);
-
         ActivityInfo info = new ActivityInfo();
         info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        when(intent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
+        ResolveInfo ri = new ResolveInfo();
+        ri.activityInfo = info;
+        when(mPackageManagerClient.resolveActivity(any(), anyInt())).thenReturn(ri);
 
         return new Notification.BubbleMetadata.Builder(
-                pendingIntent,
+                mActivityIntent,
                 Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon))
                 .build();
     }
@@ -1189,7 +1259,8 @@
 
         // Notification that has bubble metadata
         NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
-                mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
+                mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */,
+                true);
 
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrBubble.getSbn().getTag(),
                 nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(),
@@ -1203,7 +1274,8 @@
 
         // Notification without bubble metadata
         NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
-                mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+                mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */,
+                true);
 
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrPlain.getSbn().getTag(),
                 nrPlain.getSbn().getId(), nrPlain.getSbn().getNotification(),
@@ -1215,7 +1287,8 @@
 
         // Summary notification for both of those
         NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
-                mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+                mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */,
+                true);
 
         if (summaryAutoCancel) {
             nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
@@ -1232,6 +1305,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
     public void testLimitTimeOutBroadcast() {
         NotificationChannel channel = new NotificationChannel("id", "name",
                 NotificationManager.IMPORTANCE_HIGH);
@@ -2501,8 +2575,8 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testCancelWithTagDoesNotCancelLifetimeExtended() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         final NotificationRecord notif = generateNotificationRecord(null);
         notif.getSbn().getNotification().flags =
                 Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -2529,19 +2603,11 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
-
-        mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-        mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
-                sbn.getUserId());
-        waitForIdle();
-
-        assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(0);
-        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testCancelAllDoesNotCancelLifetimeExtended() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         // Adds a lifetime extended notification.
         final NotificationRecord notif = generateNotificationRecord(mTestNotificationChannel, 1,
                 null, false);
@@ -2978,9 +3044,9 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt()
             throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         final NotificationRecord notif = generateNotificationRecord(
                 mTestNotificationChannel, 1, null, false);
         notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -3211,9 +3277,9 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testCancelNotificationsFromListener_byKey_NoClearLifetimeExt()
             throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         final NotificationRecord notif = generateNotificationRecord(
                 mTestNotificationChannel, 3, null, false);
         notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -5695,12 +5761,16 @@
         verify(mZenModeHelper).updateZenRulesOnLocaleChange();
     }
 
-    private void simulateNotificationTimeoutBroadcast(String notificationKey) {
-        final Bundle extras = new Bundle();
-        extras.putString(EXTRA_KEY, notificationKey);
-        final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
-        intent.putExtras(extras);
-        mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+    private void simulateNotificationTimeout(String notificationKey) {
+        if (Flags.allNotifsNeedTtl()) {
+            mService.mNotificationManagerPrivate.timeoutNotification(notificationKey);
+        } else {
+            final Bundle extras = new Bundle();
+            extras.putString(EXTRA_KEY, notificationKey);
+            final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
+            intent.putExtras(extras);
+            mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+        }
     }
 
     @Test
@@ -5709,7 +5779,7 @@
                 mTestNotificationChannel, 1, null, false);
         mService.addNotification(notif);
 
-        simulateNotificationTimeoutBroadcast(notif.getKey());
+        simulateNotificationTimeout(notif.getKey());
         waitForIdle();
 
         // Check that the notification was cancelled.
@@ -5725,7 +5795,7 @@
         notif.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
         mService.addNotification(notif);
 
-        simulateNotificationTimeoutBroadcast(notif.getKey());
+        simulateNotificationTimeout(notif.getKey());
         waitForIdle();
 
         // Check that the notification was not cancelled.
@@ -5741,7 +5811,7 @@
         notif.getSbn().getNotification().flags = Notification.FLAG_USER_INITIATED_JOB;
         mService.addNotification(notif);
 
-        simulateNotificationTimeoutBroadcast(notif.getKey());
+        simulateNotificationTimeout(notif.getKey());
         waitForIdle();
 
         // Check that the notification was not cancelled.
@@ -5751,15 +5821,15 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testTimeout_NoCancelLifetimeExtensionNotification() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         // Create a notification with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
         final NotificationRecord notif = generateNotificationRecord(null);
         notif.getSbn().getNotification().flags =
                 Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
         mService.addNotification(notif);
 
-        simulateNotificationTimeoutBroadcast(notif.getKey());
+        simulateNotificationTimeout(notif.getKey());
         waitForIdle();
 
         // Check that the notification was not cancelled.
@@ -5839,8 +5909,8 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testStats_DirectReplyLifetimeExtendedPostsUpdate() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
         mService.addNotification(r);
@@ -6662,6 +6732,7 @@
         verify(visitor, times(1)).accept(eq(personIcon.getUri()));
         verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
         verify(visitor, times(1)).accept(eq(hangUpUri));
+        hangUpIntent.cancel();
     }
 
     @Test
@@ -6691,6 +6762,8 @@
         verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
         verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData()));
         verify(visitor, times(1)).accept(eq(declineUri));
+        answerIntent.cancel();
+        declineIntent.cancel();
     }
 
     @Test
@@ -6767,6 +6840,9 @@
         verify(visitor, times(1)).accept(eq(actionIntentUri));
         verify(visitor).accept(eq(wearActionIcon.getUri()));
         verify(visitor, times(1)).accept(eq(wearActionIntentUri));
+        displayIntent.cancel();
+        actionIntent.cancel();
+        wearActionIntent.cancel();
     }
 
     @Test
@@ -6788,6 +6864,8 @@
 
         verify(visitor, times(1)).accept(eq(contentIntentUri));
         verify(visitor, times(1)).accept(eq(deleteIntentUri));
+        contentIntent.cancel();
+        deleteIntent.cancel();
     }
 
     @Test
@@ -6813,6 +6891,8 @@
 
         verify(visitor, times(1)).accept(eq(readPendingIntentUri));
         verify(visitor, times(1)).accept(eq(replyPendingIntentUri));
+        readPendingIntent.cancel();
+        replyPendingIntent.cancel();
     }
 
     @Test
@@ -8587,8 +8667,8 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testOnNotificationSmartReplySent() {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         final int replyIndex = 2;
         final String reply = "Hello";
         final boolean modifiedBeforeSending = true;
@@ -8613,8 +8693,8 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testStats_SmartReplyAlreadyLifetimeExtendedPostsUpdate() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         final int replyIndex = 2;
         final String reply = "Hello";
         final boolean modifiedBeforeSending = true;
@@ -8647,8 +8727,7 @@
     public void testOnNotificationActionClick() {
         final int actionIndex = 2;
         final Notification.Action action =
-                new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
-                        mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+                new Notification.Action.Builder(null, "text", mActivityIntent).build();
         final boolean generatedByAssistant = false;
 
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -8669,9 +8748,8 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testActionClickLifetimeExtendedCancel() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-
         final Notification.Action action =
                 new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
                         mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
@@ -8797,8 +8875,7 @@
     public void testOnAssistantNotificationActionClick() {
         final int actionIndex = 1;
         final Notification.Action action =
-                new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
-                        mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+                new Notification.Action.Builder(null, "text", mActivityIntent).build();
         final boolean generatedByAssistant = true;
 
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -9313,7 +9390,7 @@
                 BUBBLE_PREFERENCE_ALL /* app */,
                 true /* channel */);
 
-        Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false);
+        Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false, true);
         nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
                 null, mUid, 0,
@@ -9357,7 +9434,7 @@
 
         // Messaging notif WITHOUT bubble metadata
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
 
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
                 "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
@@ -10615,7 +10692,7 @@
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(VALID_CONVO_SHORTCUT_ID).build();
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
         nb.setBubbleMetadata(metadata);
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -10675,7 +10752,7 @@
         Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
                 shortcutId).build();
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(shortcutId);
         nb.setBubbleMetadata(metadata);
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11068,7 +11145,7 @@
 
         //Create notification record
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
         nb.setChannelId(originalChannel.getId());
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11104,7 +11181,7 @@
 
         //Create notification record
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
         nb.setChannelId(originalChannel.getId());
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11148,7 +11225,7 @@
 
         //Create notification record
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
         nb.setChannelId(originalChannel.getId());
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11193,7 +11270,7 @@
 
         //Create notification record without a shortcutId
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(null);
         nb.setChannelId(originalChannel.getId());
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11328,7 +11405,7 @@
     @Test
     public void testRecordMessages_invalidMsg() throws RemoteException {
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
                 "testRecordMessages_invalidMsg", mUid, 0, nb.build(),
@@ -11369,7 +11446,7 @@
     @Test
     public void testRecordMessages_validMsg() throws RemoteException {
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
                 "testRecordMessages_validMsg", mUid, 0, nb.build(),
@@ -11405,7 +11482,7 @@
         waitForIdle();
 
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
-                null /* groupKey */, false /* isSummary */);
+                null /* groupKey */, false /* isSummary */, true);
         nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
                 "testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
@@ -11625,10 +11702,9 @@
 
     @Test
     public void testImmutableBubbleIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(pi1))
-                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
         NotificationRecord r = generateMessageBubbleNotifRecord(true,
-                mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
+                mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false, false);
         try {
             mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
                     r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11642,10 +11718,8 @@
 
     @Test
     public void testMutableBubbleIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(pi1))
-                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
         NotificationRecord r = generateMessageBubbleNotifRecord(true,
-                mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);
+                mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false, true);
 
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
                 r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11658,10 +11732,10 @@
 
     @Test
     public void testImmutableDirectReplyActionIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
         NotificationRecord r = generateMessageBubbleNotifRecord(false,
-                mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
+                mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false,
+                false);
         try {
             mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
                     r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11675,10 +11749,9 @@
 
     @Test
     public void testMutableDirectReplyActionIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
         NotificationRecord r = generateMessageBubbleNotifRecord(false,
-                mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
+                mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false,
+                true);
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
                 r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
 
@@ -11690,18 +11763,15 @@
 
     @Test
     public void testImmutableDirectReplyContextualActionIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
 
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         ArrayList<Notification.Action> extraAction = new ArrayList<>();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
-                PendingIntent.FLAG_IMMUTABLE);
         Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
+                mActivityIntentImmutable).addRemoteInput(remoteInput)
                 .build();
         extraAction.add(replyAction);
         Bundle signals = new Bundle();
@@ -11722,18 +11792,13 @@
 
     @Test
     public void testMutableDirectReplyContextualActionIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         ArrayList<Notification.Action> extraAction = new ArrayList<>();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent().setPackage(mContext.getPackageName()),
-                PendingIntent.FLAG_MUTABLE);
         Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
+                mActivityIntent).addRemoteInput(remoteInput)
                 .build();
         extraAction.add(replyAction);
         Bundle signals = new Bundle();
@@ -11749,10 +11814,8 @@
 
     @Test
     public void testImmutableActionIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
                 r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
 
@@ -11764,12 +11827,11 @@
 
     @Test
     public void testImmutableContextualActionIntent() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         ArrayList<Notification.Action> extraAction = new ArrayList<>();
-        extraAction.add(new Notification.Action(0, "hello", null));
+        extraAction.add(new Notification.Action(0, "hello", mActivityIntentImmutable));
         Bundle signals = new Bundle();
         signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
         Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
@@ -12121,8 +12183,6 @@
 
     @Test
     public void testCallNotificationsBypassBlock() throws Exception {
-        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
-                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
 
         Notification.Builder nb = new Notification.Builder(
@@ -12145,8 +12205,8 @@
                 .setName("caller")
                 .build();
         nb.setStyle(Notification.CallStyle.forOngoingCall(
-                person, mock(PendingIntent.class)));
-        nb.setFullScreenIntent(mock(PendingIntent.class), true);
+                person, mActivityIntent));
+        nb.setFullScreenIntent(mActivityIntent, true);
         sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12214,8 +12274,8 @@
         mService.clearNotifications();
         reset(mUsageStats);
         Person person = new Person.Builder().setName("caller").build();
-        nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class)));
-        nb.setFullScreenIntent(mock(PendingIntent.class), true);
+        nb.setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent));
+        nb.setFullScreenIntent(mActivityIntent, true);
         sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0, nb.build(),
                 UserHandle.getUserHandleForUid(mUid), null, 0);
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12709,7 +12769,7 @@
                 Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                 NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
         mService.maybeShowInitialReviewPermissionsNotification();
-        verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+        verify(mMockNm, times(1)).notify(eq(TAG),
                 eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
                 any(Notification.class));
     }
@@ -12744,7 +12804,7 @@
                 Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                 NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
         mService.maybeShowInitialReviewPermissionsNotification();
-        verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+        verify(mMockNm, times(1)).notify(eq(TAG),
                 eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
                 any(Notification.class));
     }
@@ -12760,7 +12820,7 @@
         mInternalService.sendReviewPermissionsNotification();
 
         // Notification should be sent
-        verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+        verify(mMockNm, times(1)).notify(eq(TAG),
                 eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
                 any(Notification.class));
 
@@ -12792,7 +12852,7 @@
                 .thenReturn(permissionState);
 
         Notification n = new Notification.Builder(mContext, "test")
-                .setFullScreenIntent(mock(PendingIntent.class), true)
+                .setFullScreenIntent(mActivityIntent, true)
                 .build();
 
         mService.fixNotification(n, mPkg, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
@@ -12860,7 +12920,7 @@
         Person person = new Person.Builder().setName("caller").build();
         Notification n = new Notification.Builder(mContext, "test")
                 .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                        person, mActivityIntent))
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -12881,7 +12941,7 @@
         Notification n = new Notification.Builder(mContext, "test")
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                        person, mActivityIntent))
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13040,8 +13100,7 @@
         Notification n = new Notification.Builder(mContext, "test")
                 // Without FLAG_FOREGROUND_SERVICE.
                 //.setFlag(FLAG_FOREGROUND_SERVICE, true)
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13057,8 +13116,7 @@
         Person person = new Person.Builder().setName("caller").build();
         Notification n = new Notification.Builder(mContext, "test")
                 .setFlag(FLAG_USER_INITIATED_JOB, true)
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13072,9 +13130,8 @@
     public void checkCallStyleNotification_allowedForFsiAllowed() throws Exception {
         Person person = new Person.Builder().setName("caller").build();
         Notification n = new Notification.Builder(mContext, "test")
-                .setFullScreenIntent(mock(PendingIntent.class), true)
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                .setFullScreenIntent(mActivityIntent, true)
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13089,8 +13146,7 @@
         Person person = new Person.Builder().setName("caller").build();
         Notification n = new Notification.Builder(mContext, "test")
                 .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13170,6 +13226,33 @@
     }
 
     @Test
+    public void fixSystemNotification_defaultAdservices_withOnGoingFlag_nondismissible()
+            throws Exception {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = ADSERVICES_APK_PKG;
+        ai.uid = mUid;
+        ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(ai);
+        when(mAppOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
+                ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
+        // Given: a notification from an app on the system partition has the flag
+        // FLAG_ONGOING_EVENT set
+        Notification n = new Notification.Builder(mContext, "test")
+                .setOngoing(true)
+                .build();
+
+        // When: fix the notification with NotificationManagerService
+        mService.fixNotification(n, ADSERVICES_APK_PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE,
+                 true);
+
+        // Then: the notification's flag FLAG_NO_DISMISS should be set
+        assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+    }
+
+    @Test
     public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
             throws Exception {
         // Given: a call notification has the flag FLAG_ONGOING_EVENT set
@@ -13178,8 +13261,7 @@
                 .build();
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                        person, mock(PendingIntent.class)))
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
                 .build();
 
         // When: fix the notification with NotificationManagerService
@@ -14338,6 +14420,9 @@
                 eq(REASON_NOTIFICATION_SERVICE), any());
         verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
                 any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+        contentIntent.cancel();
+        actionIntent2.cancel();
+        actionIntent1.cancel();
     }
 
     @Test
@@ -14366,6 +14451,10 @@
                 eq(REASON_NOTIFICATION_SERVICE), any());
         verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
                 any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+        contentIntent.cancel();
+        publicContentIntent.cancel();
+        actionIntent.cancel();
+        publicActionIntent.cancel();
     }
 
     @Test
@@ -14891,8 +14980,8 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testFixNotification_clearsLifetimeExtendedFlag() throws Exception {
-        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         Notification n = new Notification.Builder(mContext, "test")
                 .setFlag(FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
                 .build();
@@ -15321,7 +15410,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+    @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
     public void testFixNotification_missingTtl() throws Exception {
         Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15333,7 +15422,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+    @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
     public void testFixNotification_doesNotOverwriteTtl() throws Exception {
         Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15351,8 +15440,7 @@
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
                 .setFlag(FLAG_USER_INITIATED_JOB, true)
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                    person, mock(PendingIntent.class)))
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
         StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
                 testName, mUid, 0, nb.build(), userHandle, null, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index aeeca2ae..5033a380 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3981,7 +3981,7 @@
         pm.applicationInfo = new ApplicationInfo();
         pm.applicationInfo.uid = UID_O;
         List<PackageInfo> packages = ImmutableList.of(pm);
-        when(mPm.getInstalledPackagesAsUser(any(), anyInt())).thenReturn(packages);
+        when(mPm.getInstalledPackagesAsUser(eq(0), anyInt())).thenReturn(packages);
         mHelper.updateFixedImportance(users);
 
         assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O));
@@ -4097,7 +4097,7 @@
         pm.applicationInfo = new ApplicationInfo();
         pm.applicationInfo.uid = UID_O;
         List<PackageInfo> packages = ImmutableList.of(pm);
-        when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+        when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
         mHelper.updateFixedImportance(users);
 
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -4120,7 +4120,7 @@
         pm.applicationInfo = new ApplicationInfo();
         pm.applicationInfo.uid = UID_O;
         List<PackageInfo> packages = ImmutableList.of(pm);
-        when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+        when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
         mHelper.updateFixedImportance(users);
 
         NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -4309,7 +4309,7 @@
         pm.applicationInfo = new ApplicationInfo();
         pm.applicationInfo.uid = UID_O;
         List<PackageInfo> packages = ImmutableList.of(pm);
-        when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+        when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
         mHelper.updateFixedImportance(users);
 
         ArraySet<String> toRemove = new ArraySet<>();
@@ -4341,7 +4341,7 @@
         pm.applicationInfo = new ApplicationInfo();
         pm.applicationInfo.uid = UID_O;
         List<PackageInfo> packages = ImmutableList.of(pm);
-        when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+        when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
         mHelper.updateFixedImportance(users);
 
         assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index ad420f6..527001d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -55,6 +55,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.compat.IPlatformCompat;
 import com.android.server.UiServiceTestCase;
 
 import org.junit.Before;
@@ -155,7 +156,8 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
-                mUsageStats, new String[] {ImportanceExtractor.class.getName()});
+                mUsageStats, new String[] {ImportanceExtractor.class.getName()},
+                mock(IPlatformCompat.class));
 
         mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
new file mode 100644
index 0000000..8b46c8c
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.notification;
+
+import static com.android.server.notification.TimeToLiveHelper.EXTRA_KEY;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+public class TimeToLiveHelperTest extends UiServiceTestCase {
+
+    TimeToLiveHelper mHelper;
+    @Mock
+    NotificationManagerPrivate mNm;
+    @Mock
+    AlarmManager mAm;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(AlarmManager.class, mAm);
+        mHelper = new TimeToLiveHelper(mNm, mContext);
+    }
+
+    @After
+    public void tearDown() {
+        mHelper.destroy();
+    }
+
+    private NotificationRecord getRecord(String tag, int timeoutAfter) {
+        NotificationChannel channel = new NotificationChannel("id", "name",
+                NotificationManager.IMPORTANCE_HIGH);
+        Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setTimeoutAfter(timeoutAfter);
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, tag, mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        return new NotificationRecord(mContext, sbn, channel);
+    }
+
+    @Test
+    public void testTimeout() {
+        mHelper.scheduleTimeoutLocked(getRecord("testTimeout", 1), 1);
+
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+        assertThat(mHelper.mKeys).hasSize(1);
+    }
+
+    @Test
+    public void testTimeoutExpires() {
+        NotificationRecord r = getRecord("testTimeoutExpires", 1);
+
+        mHelper.scheduleTimeoutLocked(r, 1);
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captor.capture());
+
+        mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captor.getValue().getIntent());
+
+        assertThat(mHelper.mKeys).isEmpty();
+    }
+
+    @Test
+    public void testTimeoutExpires_twoEntries() {
+        NotificationRecord first = getRecord("testTimeoutFirst", 1);
+        NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+        mHelper.scheduleTimeoutLocked(first, 1);
+        mHelper.scheduleTimeoutLocked(later, 1);
+
+        ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+
+        ArgumentCaptor<PendingIntent> captorNewSet = ArgumentCaptor.forClass(PendingIntent.class);
+        mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captorSet.getValue().getIntent());
+
+        assertThat(mHelper.mKeys).hasSize(1);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorNewSet.capture());
+        assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(first.getKey());
+    }
+
+    @Test
+    public void testTimeout_earlierEntryAddedSecond() {
+        NotificationRecord later = getRecord("testTimeoutSecond", 2);
+        mHelper.scheduleTimeoutLocked(later, 1);
+
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), any());
+        assertThat(mHelper.mKeys).hasSize(1);
+
+        NotificationRecord first = getRecord("testTimeoutFirst", 1);
+        ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+        ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mHelper.scheduleTimeoutLocked(first, 1);
+
+        assertThat(mHelper.mKeys).hasSize(2);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+        assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(first.getKey());
+        assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+
+        verify(mAm).cancel(captorCancel.capture());
+        assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(later.getKey());
+    }
+
+    @Test
+    public void testTimeout_earlierEntryAddedFirst() {
+        NotificationRecord first = getRecord("testTimeoutFirst", 1);
+        NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+        mHelper.scheduleTimeoutLocked(first, 1);
+        mHelper.scheduleTimeoutLocked(later, 1);
+
+        assertThat(mHelper.mKeys).hasSize(2);
+        assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+        verify(mAm, never()).cancel((PendingIntent) any());
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+    }
+
+    @Test
+    public void testTimeout_updateEarliestEntry() {
+        NotificationRecord first = getRecord("testTimeoutFirst", 1);
+
+        mHelper.scheduleTimeoutLocked(first, 1);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+        NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+        ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+        ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+        assertThat(mHelper.mKeys).hasSize(1);
+
+        // cancel original alarm
+        verify(mAm).cancel(captorCancel.capture());
+        assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(first.getKey());
+
+        // schedule later alarm
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(4L), captorSet.capture());
+        assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(first.getKey());
+    }
+
+    @Test
+    public void testTimeout_twoEntries_updateEarliestEntry() {
+        NotificationRecord first = getRecord("testTimeoutFirst", 1);
+        NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+        mHelper.scheduleTimeoutLocked(first, 1);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+        mHelper.scheduleTimeoutLocked(later, 1);
+
+        NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+        ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+        ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+        assertThat(mHelper.mKeys).hasSize(2);
+        assertThat(mHelper.mKeys.first().second).isEqualTo(later.getKey());
+
+        // "first" was canceled because it's now later
+        verify(mAm).cancel(captorCancel.capture());
+        assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(first.getKey());
+
+        // "later" is now the first entry, and needs the matching alarm
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorSet.capture());
+        assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+                .isEqualTo(later.getKey());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2c88ed2..7356b43 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1717,6 +1717,7 @@
         // The display should be rotated after the launch is finished.
         doReturn(false).when(app).isAnimating(anyInt(), anyInt());
         mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+        waitHandlerIdle(mWm.mH);
         mStatusBarWindow.finishSeamlessRotation(t);
         mNavBarWindow.finishSeamlessRotation(t);
 
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 80e169d..b90fa21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -25,6 +25,8 @@
 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 org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -37,6 +39,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.testing.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -288,4 +292,56 @@
                 false /* forTabletopMode */,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
     }
+
+    @Test
+    public void test_setLetterboxHorizontalPositionMultiplier_validValues() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(-1));
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(2));
+
+        // Does not throw an exception for values [0,1].
+        mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0);
+        mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+        mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(1);
+    }
+
+    @Test
+    public void test_setLetterboxVerticalPositionMultiplier_validValues() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(-1));
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(2));
+
+        // Does not throw an exception for values [0,1].
+        mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0);
+        mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+        mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1);
+    }
+
+    @Test
+    public void test_setLetterboxBookModePositionMultiplier_validValues() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(-1));
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(2));
+
+        // Does not throw an exception for values [0,1].
+        mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0);
+        mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0.5f);
+        mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(1);
+    }
+
+    @Test
+    public void test_setLetterboxTabletopModePositionMultiplier_validValues() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(-1));
+        assertThrows(IllegalArgumentException.class,
+                () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(2));
+
+        // Does not throw an exception for values [0,1].
+        mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0);
+        mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
+        mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 649f520..dcf3dad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -74,6 +74,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
@@ -237,6 +238,24 @@
         assertFalse(wpc.hasActivities());
     }
 
+    @Test
+    public void testAttachApplication() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity.detachFromProcess();
+        mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+                true /* isTop */, "test" /* hostingType */);
+        final WindowProcessController proc = mSystemServicesTestRule.addProcess(
+                activity.packageName, activity.processName,
+                6789 /* pid */, activity.info.applicationInfo.uid);
+        try {
+            mRootWindowContainer.attachApplication(proc);
+            verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
+                    anyBoolean());
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
     /**
      * This test ensures that we do not try to restore a task based off an invalid task id. We
      * should expect {@code null} to be returned in this case.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fbf1426..2e80bc7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4143,31 +4143,6 @@
     }
 
     @Test
-    public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
-        // Display configured as (2800, 1400).
-
-        // Below 0.0.
-        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
-                /* letterboxHorizontalPositionMultiplier */ -1.0f,
-                // At launch.
-                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
-                // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
-                // After the display is resized to (700, 1400).
-                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
-
-        // Above 1.0
-        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
-                /* letterboxHorizontalPositionMultiplier */ 2.0f,
-                // At launch.
-                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
-                // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
-                // After the display is resized to (700, 1400).
-                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
-    }
-
-    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_right() {
         // Display configured as (2800, 1400).
         assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -4398,31 +4373,6 @@
     }
 
     @Test
-    public void testUpdateResolvedBoundsVerticalPosition_invalidMultiplier_defaultToCenter() {
-        // Display configured as (1400, 2800).
-
-        // Below 0.0.
-        assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
-                /* letterboxVerticalPositionMultiplier */ -1.0f,
-                // At launch.
-                /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
-                // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
-                // After the display is resized to (1400, 700).
-                /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
-
-        // Above 1.0
-        assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
-                /* letterboxVerticalPositionMultiplier */ 2.0f,
-                // At launch.
-                /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
-                // After 90 degree rotation.
-                /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
-                // After the display is resized to (1400, 700).
-                /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
-    }
-
-    @Test
     public void testUpdateResolvedBoundsVerticalPosition_bottom() {
         // Display configured as (1400, 2800).
         assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 413d003..b9fe074 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -68,6 +68,7 @@
 import android.os.PowerSaveState;
 import android.os.StrictMode;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.util.Log;
 import android.view.DisplayInfo;
@@ -194,6 +195,7 @@
         mMockitoSession = mockitoSession()
                 .mockStatic(LocalServices.class, spyStubOnly)
                 .mockStatic(DeviceConfig.class, spyStubOnly)
+                .mockStatic(UserManager.class, spyStubOnly)
                 .mockStatic(SurfaceControl.class, mockStubOnly)
                 .mockStatic(DisplayControl.class, mockStubOnly)
                 .mockStatic(LockGuard.class, mockStubOnly)
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 52485ee..002a3d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -19,6 +19,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1024,6 +1026,58 @@
     }
 
     @Test
+    public void testApplyTransaction_createTaskFragment_overrideOrientation_systemOrganizer() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
+        mController.unregisterOrganizer(mIOrganizer);
+        registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(task);
+        final int uid = Binder.getCallingUid();
+        activity.info.applicationInfo.uid = uid;
+        activity.getTask().effectiveUid = uid;
+        final IBinder fragmentToken = new Binder();
+
+        // Create a TaskFragment with OverrideOrientation set.
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken, activity.token)
+                .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+                .build();
+        mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+        mTransaction.createTaskFragment(params);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // TaskFragment override orientation should be set for a system organizer.
+        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+        assertNotNull(taskFragment);
+        assertEquals(SCREEN_ORIENTATION_BEHIND, taskFragment.getOverrideOrientation());
+    }
+
+    @Test
+    public void testApplyTransaction_createTaskFragment_overrideOrientation_nonSystemOrganizer() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(task);
+        final int uid = Binder.getCallingUid();
+        activity.info.applicationInfo.uid = uid;
+        activity.getTask().effectiveUid = uid;
+        final IBinder fragmentToken = new Binder();
+
+        // Create a TaskFragment with OverrideOrientation set.
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken, activity.token)
+                .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+                .build();
+        mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+        mTransaction.createTaskFragment(params);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // TaskFragment override orientation is ignored for a non-system organizer.
+        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+        assertNotNull(taskFragment);
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, taskFragment.getOverrideOrientation());
+    }
+
+    @Test
     public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() {
         final Task task = createTask(mDisplayContent);
         final ActivityRecord activity = createActivityRecord(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3c5b12c..4837fcb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -752,6 +753,21 @@
     }
 
     @Test
+    public void testGetOrientation_reportOverrideOrientation() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf = createTaskFragmentWithActivity(task);
+        final ActivityRecord activity = tf.getTopMostActivity();
+        tf.setOverrideOrientation(SCREEN_ORIENTATION_BEHIND);
+
+        // Should report the override orientation
+        assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+        // Should report the override orientation even if the activity requests a different value
+        activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+    }
+
+    @Test
     public void testUpdateImeParentForActivityEmbedding() {
         // Setup two activities in ActivityEmbedding.
         final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 50db99e..5fe71a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -25,7 +25,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_OWN_FOCUS;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
@@ -38,6 +37,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -82,6 +82,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -1304,7 +1305,8 @@
     }
 
     @Test
-    public void testAddOverlayWindowToUnassignedDisplay_notAllowed() {
+    public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
+        doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
         int uid = 100000; // uid for non-system user
         Session session = createTestSession(mAtm, 1234 /* pid */, uid);
         DisplayContent dc = createNewDisplay();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 43b424f..69b5c37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1681,7 +1681,8 @@
         WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
         t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+                any(), any(), anyBoolean());
 
         clearInvocations(mWm.mAtmService.mRootWindowContainer);
         // The token for the PIP root task may have changed when the task entered PIP mode, so do
@@ -1690,7 +1691,8 @@
                 record.getRootTask().mRemoteToken.toWindowContainerToken();
         t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+                any(), any(), anyBoolean());
     }
 
     @Test
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0a8a18dc..f076de3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -289,7 +289,7 @@
     @SystemApi
     public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3;
 
-    /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
+    /** The otaspMode passed to SercvieState changes */
     /** @hide */
     static public final int OTASP_UNINITIALIZED = 0;
     /** @hide */
@@ -995,9 +995,9 @@
             "android.intent.action.CALL_DISCONNECT_CAUSE";
 
     /**
-     * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
-     * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
-     * containing the disconnect cause.
+     * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and {@link
+     * TelephonyCallback.PreciseCallStateListener#onPreciseCallStateChanged(PreciseCallState)} for
+     * an integer containing the disconnect cause.
      *
      * @see DisconnectCause
      *
@@ -1012,9 +1012,9 @@
     public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause";
 
     /**
-     * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
-     * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
-     * containing the disconnect cause provided by the RIL.
+     * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and {@link
+     * TelephonyCallback.PreciseCallStateListener#onPreciseCallStateChanged(PreciseCallState)} for
+     * an integer containing the disconnect cause provided by the RIL.
      *
      * @see PreciseDisconnectCause
      *
@@ -6437,8 +6437,8 @@
      * This method considers not only calls in the Telephony stack, but also calls via other
      * {@link android.telecom.ConnectionService} implementations.
      * <p>
-     * Note: The call state returned via this method may differ from what is reported by
-     * {@link PhoneStateListener#onCallStateChanged(int, String)}, as that callback only considers
+     * Note: The call state returned via this method may differ from what is reported by {@link
+     * TelephonyCallback.CallStateListener#onCallStateChanged(int)}, as that callback only considers
      * Telephony (mobile) calls.
      * <p>
      * Requires Permission:
@@ -6996,7 +6996,7 @@
      *
      * <p>Beginning with {@link android.os.Build.VERSION_CODES#Q Android Q},
      * if this API results in a change of the cached CellInfo, that change will be reported via
-     * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}.
+     * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}.
      *
      * <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no
      * longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps
@@ -7109,7 +7109,7 @@
      * camped/registered, serving, and neighboring cells.
      *
      * <p>Any available results from this request will be provided by calls to
-     * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+     * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}
      * for each active subscription.
      *
      * <p>This method returns valid data for devices with
@@ -7172,7 +7172,7 @@
      * camped/registered, serving, and neighboring cells.
      *
      * <p>Any available results from this request will be provided by calls to
-     * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+     * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}
      * for each active subscription.
      *
      * <p>This method returns valid data for devices with
@@ -7248,8 +7248,8 @@
     }
 
     /**
-     * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
-     * PhoneStateListener.onCellInfoChanged} will be invoked.
+     * Sets the minimum time in milli-seconds between {@link
+     * TelephonyCallback.CellInfoListener#onCellInfoChanged(List)} will be invoked.
      *<p>
      * The default, 0, means invoke onCellInfoChanged when any of the reported
      * information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
@@ -11364,7 +11364,7 @@
      * Shut down all the live radios over all the slot indexes.
      *
      * <p>To know when the radio has completed powering off, use
-     * {@link PhoneStateListener#LISTEN_SERVICE_STATE LISTEN_SERVICE_STATE}.
+     * {@link TelephonyCallback.ServiceStateListener}.
      *
      * @throws UnsupportedOperationException If the device does not have
      *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
@@ -13058,8 +13058,9 @@
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
      *
-     * If you want continuous updates of service state info, register a {@link PhoneStateListener}
-     * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+     * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+     * that implements {@link TelephonyCallback.ServiceStateListener} through {@link
+     * #registerTelephonyCallback}.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
@@ -13086,8 +13087,9 @@
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
      *
-     * If you want continuous updates of service state info, register a {@link PhoneStateListener}
-     * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+     * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+     * that implements {@link TelephonyCallback.ServiceStateListener} through {@link
+     * #registerTelephonyCallback}.
      *
      * There's another way to renounce permissions with a custom context
      * {@code AttributionSource.Builder#setRenouncedPermissions(Set<String>)} but only for system
@@ -17892,9 +17894,9 @@
      * measurements breach the specified thresholds.
      *
      * To be notified, set the signal strength update request and then register
-     * {@link TelephonyManager#listen(PhoneStateListener, int)} with
-     * {@link PhoneStateListener#LISTEN_SIGNAL_STRENGTHS}. The notification will arrive through
-     * {@link PhoneStateListener#onSignalStrengthsChanged(SignalStrength)}.
+     * {@link TelephonyCallback} that implements {@link TelephonyCallback.SignalStrengthsListener}
+     * through {@link #registerTelephonyCallback}. The notification will arrive through
+     * {@link TelephonyCallback.SignalStrengthsListener#onSignalStrengthsChanged(SignalStrength)}.
      *
      * To stop receiving the notification over the specified thresholds, pass the same
      * {@link SignalStrengthUpdateRequest} object to
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index f6f766a..8d2b927 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -19,17 +19,26 @@
 
 import android.content.Context
 import android.content.ContextWrapper
+import android.hardware.display.DisplayManager
 import android.hardware.display.DisplayViewport
+import android.hardware.display.VirtualDisplay
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
+import android.os.InputEventInjectionSync
+import android.os.SystemClock
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresFlagsDisabled
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.provider.Settings
-import android.test.mock.MockContentResolver
+import android.view.View.OnKeyListener
 import android.view.Display
+import android.view.InputDevice
+import android.view.KeyEvent
 import android.view.PointerIcon
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.test.mock.MockContentResolver
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.internal.util.test.FakeSettingsProvider
 import com.google.common.truth.Truth.assertThat
@@ -48,6 +57,7 @@
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
@@ -412,6 +422,174 @@
         verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
         thread.join(100 /*millis*/)
     }
+
+    private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
+        val displayManager: DisplayManager = context.getSystemService(
+                DisplayManager::class.java
+        ) as DisplayManager
+        val virtualDisplays = mutableListOf<VirtualDisplay>()
+        for (i in 0 until count) {
+            virtualDisplays.add(displayManager.createVirtualDisplay(
+                    /* displayName= */ "testVirtualDisplay$i",
+                    /* width= */ 100,
+                    /* height= */ 100,
+                    /* densityDpi= */ 100,
+                    /* surface= */ null,
+                    /* flags= */ 0
+            ))
+        }
+        return virtualDisplays
+    }
+
+    // Helper function that creates a KeyEvent with Keycode A with the given action
+    private fun createKeycodeAEvent(inputDevice: InputDevice, action: Int): KeyEvent {
+        val eventTime = SystemClock.uptimeMillis()
+        return KeyEvent(
+                /* downTime= */ eventTime,
+                /* eventTime= */ eventTime,
+                /* action= */ action,
+                /* code= */ KeyEvent.KEYCODE_A,
+                /* repeat= */ 0,
+                /* metaState= */ 0,
+                /* deviceId= */ inputDevice.id,
+                /* scanCode= */ 0,
+                /* flags= */ KeyEvent.FLAG_FROM_SYSTEM,
+                /* source= */ InputDevice.SOURCE_KEYBOARD
+        )
+    }
+
+    private fun createInputDevice(): InputDevice {
+        return InputDevice.Builder()
+                .setId(123)
+                .setName("abc")
+                .setDescriptor("def")
+                .setSources(InputDevice.SOURCE_KEYBOARD)
+                .build()
+    }
+
+    @Test
+    fun addUniqueIdAssociationByDescriptor_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.
+        // Then remove associations, then associate the InputDevice with display 2, then send
+        // and verify commands.
+
+        // Make 2 virtual displays with some mock SurfaceViews
+        val mockSurfaceView1 = mock(SurfaceView::class.java)
+        val mockSurfaceView2 = mock(SurfaceView::class.java)
+        val mockSurfaceHolder1 = mock(SurfaceHolder::class.java)
+        `when`(mockSurfaceView1.holder).thenReturn(mockSurfaceHolder1)
+        val mockSurfaceHolder2 = mock(SurfaceHolder::class.java)
+        `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2)
+
+        val virtualDisplays = createVirtualDisplays(2)
+
+        // Simulate an InputDevice
+        val inputDevice = createInputDevice()
+
+        // Associate input device with display
+        service.addUniqueIdAssociationByDescriptor(
+                inputDevice.descriptor,
+                virtualDisplays[0].display.displayId.toString()
+        )
+
+        // Simulate 2 different KeyEvents
+        val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN)
+        val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP)
+
+        // Create a mock OnKeyListener object
+        val mockOnKeyListener = mock(OnKeyListener::class.java)
+
+        // Verify that the event went to Display 1 not Display 2
+        service.injectInputEvent(downEvent, InputEventInjectionSync.NONE)
+
+        // Call the onKey method on the mock OnKeyListener object
+        mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent)
+        mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent)
+
+        // Verify that the onKey method was called with the expected arguments
+        verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent)
+        verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
+
+        // Remove association
+        service.removeUniqueIdAssociationByDescriptor(inputDevice.descriptor)
+
+        // Associate with Display 2
+        service.addUniqueIdAssociationByDescriptor(
+                inputDevice.descriptor,
+                virtualDisplays[1].display.displayId.toString()
+        )
+
+        // Simulate a KeyEvent
+        service.injectInputEvent(upEvent, InputEventInjectionSync.NONE)
+
+        // Verify that the event went to Display 2 not Display 1
+        verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
+        verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
+    }
+
+    @Test
+    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.
+        // Then remove associations, then associate the InputDevice with display 2, then send
+        // and verify commands.
+
+        // Make 2 virtual displays with some mock SurfaceViews
+        val mockSurfaceView1 = mock(SurfaceView::class.java)
+        val mockSurfaceView2 = mock(SurfaceView::class.java)
+        val mockSurfaceHolder1 = mock(SurfaceHolder::class.java)
+        `when`(mockSurfaceView1.holder).thenReturn(mockSurfaceHolder1)
+        val mockSurfaceHolder2 = mock(SurfaceHolder::class.java)
+        `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2)
+
+        val virtualDisplays = createVirtualDisplays(2)
+
+        // Simulate an InputDevice
+        val inputDevice = createInputDevice()
+
+        // Associate input device with display
+        service.addUniqueIdAssociationByPort(
+                inputDevice.name,
+                virtualDisplays[0].display.displayId.toString()
+        )
+
+        // Simulate 2 different KeyEvents
+        val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN)
+        val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP)
+
+        // Create a mock OnKeyListener object
+        val mockOnKeyListener = mock(OnKeyListener::class.java)
+
+        // Verify that the event went to Display 1 not Display 2
+        service.injectInputEvent(downEvent, InputEventInjectionSync.NONE)
+
+        // Call the onKey method on the mock OnKeyListener object
+        mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent)
+        mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent)
+
+        // Verify that the onKey method was called with the expected arguments
+        verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent)
+        verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
+
+        // Remove association
+        service.removeUniqueIdAssociationByPort(inputDevice.name)
+
+        // Associate with Display 2
+        service.addUniqueIdAssociationByPort(
+                inputDevice.name,
+                virtualDisplays[1].display.displayId.toString()
+        )
+
+        // Simulate a KeyEvent
+        service.injectInputEvent(upEvent, InputEventInjectionSync.NONE)
+
+        // Verify that the event went to Display 2 not Display 1
+        verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
+        verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
+    }
 }
 
 private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/OneMedia/Android.bp b/tests/OneMedia/Android.bp
index 5c73177..a43cd39 100644
--- a/tests/OneMedia/Android.bp
+++ b/tests/OneMedia/Android.bp
@@ -16,6 +16,7 @@
     platform_apis: true,
     certificate: "platform",
     libs: ["org.apache.http.legacy"],
+    optional_uses_libs: ["org.apache.http.legacy"],
     optimize: {
         enabled: false,
     },
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index fdf8fb8..c8b60e5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -17,9 +17,11 @@
 package com.android.server.vcn.routeselection;
 
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
 
-import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
@@ -44,6 +46,7 @@
 import android.os.OutcomeReceiver;
 import android.os.PowerManager;
 
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculationResult;
 import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
 import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
 import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
@@ -65,6 +68,7 @@
     private static final int REPLAY_BITMAP_LEN_BYTE = 512;
     private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
     private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+    private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
     private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
 
     @Mock private IpSecTransformWrapper mIpSecTransform;
@@ -91,6 +95,9 @@
                         eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
                         anyInt()))
                 .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+                .thenReturn(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
 
         when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
 
@@ -112,6 +119,20 @@
                 .build();
     }
 
+    private static IpSecTransformState newNextTransformState(
+            IpSecTransformState before,
+            long timeDiffMillis,
+            long rxSeqNoDiff,
+            long packtCountDiff,
+            int packetInWin) {
+        return new IpSecTransformState.Builder()
+                .setTimestampMillis(before.getTimestampMillis() + timeDiffMillis)
+                .setRxHighestSequenceNumber(before.getRxHighestSequenceNumber() + rxSeqNoDiff)
+                .setPacketCount(before.getPacketCount() + packtCountDiff)
+                .setReplayBitmap(newReplayBitmap(packetInWin))
+                .build();
+    }
+
     private static byte[] newReplayBitmap(int receivedPktCnt) {
         final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
         for (int i = 0; i < receivedPktCnt; i++) {
@@ -165,7 +186,7 @@
         // Verify the first polled state is stored
         assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
         verify(mPacketLossCalculator, never())
-                .getPacketLossRatePercentage(any(), any(), anyString());
+                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
 
         // Verify next poll is scheduled
         assertNull(mTestLooper.nextMessage());
@@ -278,7 +299,7 @@
 
         xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
         verify(mPacketLossCalculator, never())
-                .getPacketLossRatePercentage(any(), any(), anyString());
+                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
     }
 
     @Test
@@ -289,17 +310,19 @@
 
         xfrmStateReceiver.onError(new RuntimeException("Test"));
         verify(mPacketLossCalculator, never())
-                .getPacketLossRatePercentage(any(), any(), anyString());
+                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
     }
 
     private void checkHandleLossRate(
-            int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+            PacketLossCalculationResult mockPacketLossRate,
+            boolean isLastStateExpectedToUpdate,
+            boolean isCallbackExpected)
             throws Exception {
         final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
                 startMonitorAndCaptureStateReceiver();
         doReturn(mockPacketLossRate)
                 .when(mPacketLossCalculator)
-                .getPacketLossRatePercentage(any(), any(), anyString());
+                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
 
         // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
         final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
@@ -309,7 +332,10 @@
         // Verifications
         verify(mPacketLossCalculator)
                 .getPacketLossRatePercentage(
-                        eq(mTransformStateInitial), eq(transformNew), anyString());
+                        eq(mTransformStateInitial),
+                        eq(transformNew),
+                        eq(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED),
+                        anyString());
 
         if (isLastStateExpectedToUpdate) {
             assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
@@ -327,30 +353,53 @@
     @Test
     public void testHandleLossRate_validationPass() throws Exception {
         checkHandleLossRate(
-                2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+                PacketLossCalculationResult.valid(2),
+                true /* isLastStateExpectedToUpdate */,
+                true /* isCallbackExpected */);
     }
 
     @Test
     public void testHandleLossRate_validationFail() throws Exception {
         checkHandleLossRate(
-                22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+                PacketLossCalculationResult.valid(22),
+                true /* isLastStateExpectedToUpdate */,
+                true /* isCallbackExpected */);
         verify(mConnectivityManager).reportNetworkConnectivity(mNetwork, false);
     }
 
     @Test
     public void testHandleLossRate_resultUnavalaible() throws Exception {
         checkHandleLossRate(
-                PACKET_LOSS_UNAVALAIBLE,
+                PacketLossCalculationResult.invalid(),
                 false /* isLastStateExpectedToUpdate */,
                 false /* isCallbackExpected */);
     }
 
+    @Test
+    public void testHandleLossRate_unusualSeqNumLeap_highLossRate() throws Exception {
+        checkHandleLossRate(
+                PacketLossCalculationResult.unusualSeqNumLeap(22),
+                true /* isLastStateExpectedToUpdate */,
+                false /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_unusualSeqNumLeap_lowLossRate() throws Exception {
+        checkHandleLossRate(
+                PacketLossCalculationResult.unusualSeqNumLeap(2),
+                true /* isLastStateExpectedToUpdate */,
+                true /* isCallbackExpected */);
+    }
+
     private void checkGetPacketLossRate(
-            IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+            IpSecTransformState oldState,
+            IpSecTransformState newState,
+            PacketLossCalculationResult expectedLossRate)
             throws Exception {
         assertEquals(
                 expectedLossRate,
-                mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+                mPacketLossCalculator.getPacketLossRatePercentage(
+                        oldState, newState, MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, TAG));
     }
 
     private void checkGetPacketLossRate(
@@ -362,14 +411,45 @@
             throws Exception {
         final IpSecTransformState newState =
                 newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+        checkGetPacketLossRate(
+                oldState, newState, PacketLossCalculationResult.valid(expectedDataLossRate));
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState,
+            int rxSeqNo,
+            int packetCount,
+            int packetInWin,
+            PacketLossCalculationResult expectedDataLossRate)
+            throws Exception {
+        final IpSecTransformState newState =
+                newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
         checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
     }
 
     @Test
     public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
         checkGetPacketLossRate(
-                mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
-        checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+                mTransformStateInitial,
+                mTransformStateInitial,
+                PacketLossCalculationResult.invalid());
+        checkGetPacketLossRate(
+                mTransformStateInitial, 3000, 2000, 2000, PacketLossCalculationResult.invalid());
+    }
+
+    @Test
+    public void testGetPacketLossRate_expectedPacketNumTooFew() throws Exception {
+        final int oldRxNo = 4096;
+        final int oldPktCnt = 4096;
+        final int pktCntDiff = MIN_VALID_EXPECTED_RX_PACKET_NUM - 1;
+        final byte[] bitmapReceiveAll = newReplayBitmap(4096);
+
+        final IpSecTransformState oldState =
+                newTransformState(oldRxNo, oldPktCnt, bitmapReceiveAll);
+        final IpSecTransformState newState =
+                newTransformState(oldRxNo + pktCntDiff, oldPktCnt + pktCntDiff, bitmapReceiveAll);
+
+        checkGetPacketLossRate(oldState, newState, PacketLossCalculationResult.invalid());
     }
 
     @Test
@@ -419,6 +499,45 @@
         checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
     }
 
+    private void checkGetPktLossRate_unusualSeqNumLeap(
+            int maxSeqNumIncreasePerSecond,
+            int timeDiffMillis,
+            int rxSeqNoDiff,
+            PacketLossCalculationResult expected)
+            throws Exception {
+        final IpSecTransformState oldState = mTransformStateInitial;
+        final IpSecTransformState newState =
+                newNextTransformState(
+                        oldState,
+                        timeDiffMillis,
+                        rxSeqNoDiff,
+                        1 /* packtCountDiff */,
+                        1 /* packetInWin */);
+
+        assertEquals(
+                expected,
+                mPacketLossCalculator.getPacketLossRatePercentage(
+                        oldState, newState, maxSeqNumIncreasePerSecond, TAG));
+    }
+
+    @Test
+    public void testGetPktLossRate_unusualSeqNumLeap() throws Exception {
+        checkGetPktLossRate_unusualSeqNumLeap(
+                10000 /* maxSeqNumIncreasePerSecond */,
+                (int) TimeUnit.SECONDS.toMillis(2L),
+                30000 /* rxSeqNoDiff */,
+                PacketLossCalculationResult.unusualSeqNumLeap(100));
+    }
+
+    @Test
+    public void testGetPktLossRate_unusualSeqNumLeap_smallSeqNumDiff() throws Exception {
+        checkGetPktLossRate_unusualSeqNumLeap(
+                10000 /* maxSeqNumIncreasePerSecond */,
+                (int) TimeUnit.SECONDS.toMillis(2L),
+                5000 /* rxSeqNoDiff */,
+                PacketLossCalculationResult.valid(100));
+    }
+
     // Verify the polling event is scheduled with expected delays
     private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) {
         if (expectedDelayMs > 0) {
@@ -445,4 +564,24 @@
         // Verify the 3rd poll is scheduled with configured delay
         verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS);
     }
+
+    @Test
+    public void testGetMaxSeqNumIncreasePerSecond() throws Exception {
+        final int seqNumLeapNegative = 500_000;
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+                .thenReturn(seqNumLeapNegative);
+        assertEquals(seqNumLeapNegative, getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+    }
+
+    @Test
+    public void testGetMaxSeqNumIncreasePerSecond_negativeValue() throws Exception {
+        final int seqNumLeapNegative = -10;
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+                .thenReturn(seqNumLeapNegative);
+        assertEquals(
+                MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
+                getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index af6daa1..6189fb0 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,6 +123,7 @@
 
         mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
         mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
 
         when(mNetwork.getNetId()).thenReturn(-1);