Merge "Remove VibrationXmlParser @TestApi annotations" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 5456c15..46f426b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12494,6 +12494,7 @@
     method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public final android.content.pm.LauncherUserInfo getLauncherUserInfo(@NonNull android.os.UserHandle);
     method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
     method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<java.lang.String> getPreInstalledSystemPackages(@NonNull android.os.UserHandle);
+    method @FlaggedApi("android.os.get_private_space_settings") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.IntentSender getPrivateSpaceSettingsIntent();
     method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.os.UserHandle> getProfiles();
     method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
     method @Nullable public android.content.IntentSender getShortcutConfigActivityIntent(@NonNull android.content.pm.LauncherActivityInfo);
@@ -20355,6 +20356,7 @@
     method public android.view.Surface getSurface();
     method public void release();
     method public void resize(int, int, int);
+    method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_rotation_api") public void setRotation(int);
     method public void setSurface(android.view.Surface);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index abc2224..36a335e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5162,10 +5162,12 @@
   }
 
   public final class VirtualDisplayConfig implements android.os.Parcelable {
+    method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout();
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported();
   }
 
   public static final class VirtualDisplayConfig.Builder {
+    method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
   }
 
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index ef8501f..aca9bb4 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -297,6 +297,12 @@
     public boolean isTopActivityTransparent;
 
     /**
+     * Whether the top activity has specified style floating.
+     * @hide
+     */
+    public boolean isTopActivityStyleFloating;
+
+    /**
      * Encapsulate specific App Compat information.
      * @hide
      */
@@ -429,6 +435,7 @@
                 && parentTaskId == that.parentTaskId
                 && Objects.equals(topActivity, that.topActivity)
                 && isTopActivityTransparent == that.isTopActivityTransparent
+                && isTopActivityStyleFloating == that.isTopActivityStyleFloating
                 && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
     }
 
@@ -498,6 +505,7 @@
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
         isTopActivityTransparent = source.readBoolean();
+        isTopActivityStyleFloating = source.readBoolean();
         appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
     }
 
@@ -545,6 +553,7 @@
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
         dest.writeBoolean(isTopActivityTransparent);
+        dest.writeBoolean(isTopActivityStyleFloating);
         dest.writeTypedObject(appCompatTaskInfo, flags);
     }
 
@@ -582,6 +591,7 @@
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " isTopActivityTransparent=" + isTopActivityTransparent
+                + " isTopActivityStyleFloating=" + isTopActivityStyleFloating
                 + " appCompatTaskInfo=" + appCompatTaskInfo
                 + "}";
     }
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 64d2081..b63e2cf 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -32,6 +32,13 @@
 }
 
 flag {
+    namespace: "virtual_devices"
+    name: "virtual_display_insets"
+    description: "APIs for specifying virtual display insets (via cutout)"
+    bug: "350007135"
+}
+
+flag {
      namespace: "virtual_devices"
      name: "metrics_collection"
      description: "Enable collection of VDM-related metrics"
@@ -89,3 +96,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "virtual_devices"
+    name: "virtual_display_rotation_api"
+    description: "API for on-demand rotation of virtual displays"
+    bug: "291748430"
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ab58f43..8365840 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2925,10 +2925,12 @@
      */
     @SuppressWarnings("HiddenAbstractMethod")
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    public abstract void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent,
+    public void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent,
             UserHandle user, String[] receiverPermissions, int appOp, Bundle options,
             BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
-            String initialData, Bundle initialExtras);
+            String initialData, Bundle initialExtras) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
 
     /**
      * Version of
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index df27f9d..52c84dc 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -935,12 +935,11 @@
      *
      * @return {@link IntentSender} object which launches the Private Space Settings Activity, if
      * successful, null otherwise.
-     * @hide
      */
     // Alternatively, a system app can access this api for private profile if they've been granted
     // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @Nullable
-    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    @FlaggedApi(Flags.FLAG_GET_PRIVATE_SPACE_SETTINGS)
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
     public IntentSender getPrivateSpaceSettingsIntent() {
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 124d07f..c7d94c6 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -151,6 +151,16 @@
 }
 
 flag {
+    name: "fix_get_user_property_cache"
+    namespace: "multiuser"
+    description: "Cache is not optimised for getUserProperty for values below 0, eg. UserHandler.USER_NULL or UserHandle.USER_ALL"
+    bug: "350416200"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "cache_quiet_mode_state"
     namespace: "multiuser"
     description: "Optimise quiet mode state retrieval"
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 50d976f..4935389 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1423,7 +1423,9 @@
      *         {@code false} otherwise.
      * @throws UnsupportedOperationException if the query operation is not supported by the camera
      *                                       device
-     * @throws IllegalArgumentException if the session configuration is invalid
+     * @throws IllegalArgumentException if the session configuration is invalid, including, if it
+     *                                  contains certain non-supported features queryable via
+     *                                  CameraCharacteristics.
      * @throws CameraAccessException if the camera device is no longer connected or has
      *                               encountered a fatal error
      * @throws IllegalStateException if the camera device has been closed
@@ -1691,12 +1693,11 @@
          *
          * <p><b>IMPORTANT:</b></p>
          * <ul>
-         * <li>If feature support can be queried via
-         * {@link CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS} or
-         * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP}, applications should
-         * directly use that route rather than calling this function as: (1) using
-         * {@code CameraCharacteristics} is more efficient, and (2) calling this function with
-         * certain non-supported features will throw a {@link IllegalArgumentException}.</li>
+         * <li>If feature support can be queried via {@link CameraCharacteristics}, applications
+         * should directly use that route rather than calling this function as: (1) using
+         * {@code CameraCharacteristics} is more efficient, and (2) querying a feature explicitly
+         * deemed unsupported by CameraCharacteristics may throw a
+         * {@link IllegalArgumentException}.</li>
          *
          * <li>To minimize {@link SessionConfiguration} creation latency due to its dependency on
          * output surfaces, the application can call this method before acquiring valid
@@ -1724,7 +1725,8 @@
          *
          * @throws CameraAccessException if the camera device is no longer connected or has
          * encountered a fatal error
-         * @throws IllegalArgumentException if the session configuration is invalid
+         * @throws IllegalArgumentException if the session configuration is invalid, including,
+         * if it contains certain non-supported features queryable via CameraCharacteristics.
          *
          * @see CameraCharacteristics#INFO_SESSION_CONFIGURATION_QUERY_VERSION
          * @see SessionConfiguration
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 8748bb1..e9cd37a 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -817,6 +817,14 @@
         }
     }
 
+    void setVirtualDisplayRotation(IVirtualDisplayCallback token, @Surface.Rotation int rotation) {
+        try {
+            mDm.setVirtualDisplayRotation(token, rotation);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Gets the stable device display size, in pixels.
      */
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 28c71d9..77277ee 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -117,6 +117,9 @@
     // No permissions required but must be same Uid as the creator.
     void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
 
+    // No permissions required but must be same Uid as the creator.
+    void setVirtualDisplayRotation(in IVirtualDisplayCallback token, int rotation);
+
     // Get a stable metric for the device's display size. No permissions required.
     Point getStableDisplaySize();
 
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 051ce63..6cc938f 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.display;
 
+import android.annotation.FlaggedApi;
 import android.view.Display;
 import android.view.Surface;
 
@@ -122,6 +123,28 @@
         }
     }
 
+    /**
+     * Sets the rotation of the virtual display.
+     *
+     * @param rotation the new rotation of the display. May be one of {@link Surface#ROTATION_0},
+     *     {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+     *     Upon creation, the rotation of the virtual display is always {@link Surface#ROTATION_0}.
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_ROTATION_API)
+    public void setRotation(@Surface.Rotation int rotation) {
+        if (!android.companion.virtualdevice.flags.Flags.virtualDisplayRotationApi()) {
+            return;
+        }
+        if (rotation != Surface.ROTATION_0 && rotation != Surface.ROTATION_90
+                && rotation != Surface.ROTATION_180 && rotation != Surface.ROTATION_270) {
+            throw new IllegalArgumentException(
+                    "Invalid virtual display rotation value: " + rotation);
+        }
+        if (mToken != null && mDisplay.getRotation() != rotation) {
+            mGlobal.setVirtualDisplayRotation(mToken, rotation);
+        }
+    }
+
     @Override
     public String toString() {
         return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 56f69a6..b0994e6 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -31,6 +31,7 @@
 import android.os.Parcelable;
 import android.util.ArraySet;
 import android.view.Display;
+import android.view.DisplayCutout;
 import android.view.Surface;
 
 import java.util.Collections;
@@ -55,9 +56,10 @@
     private final String mUniqueId;
     private final int mDisplayIdToMirror;
     private final boolean mWindowManagerMirroringEnabled;
-    private ArraySet<String> mDisplayCategories = null;
+    private final ArraySet<String> mDisplayCategories;
     private final float mRequestedRefreshRate;
     private final boolean mIsHomeSupported;
+    private final DisplayCutout mDisplayCutout;
 
     private VirtualDisplayConfig(
             @NonNull String name,
@@ -71,7 +73,8 @@
             boolean windowManagerMirroringEnabled,
             @NonNull ArraySet<String> displayCategories,
             float requestedRefreshRate,
-            boolean isHomeSupported) {
+            boolean isHomeSupported,
+            @Nullable DisplayCutout displayCutout) {
         mName = name;
         mWidth = width;
         mHeight = height;
@@ -84,6 +87,7 @@
         mDisplayCategories = displayCategories;
         mRequestedRefreshRate = requestedRefreshRate;
         mIsHomeSupported = isHomeSupported;
+        mDisplayCutout = displayCutout;
     }
 
     /**
@@ -135,6 +139,21 @@
     }
 
     /**
+     * Returns the cutout of this display.
+     *
+     * @return the cutout of the display or {@code null} if none is specified.
+     *
+     * @see Builder#setDisplayCutout
+     * @hide
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_INSETS)
+    @SystemApi
+    @Nullable
+    public DisplayCutout getDisplayCutout() {
+        return mDisplayCutout;
+    }
+
+    /**
      * Returns the unique identifier for the display. Shouldn't be displayed to the user.
      * @hide
      */
@@ -207,6 +226,7 @@
         dest.writeArraySet(mDisplayCategories);
         dest.writeFloat(mRequestedRefreshRate);
         dest.writeBoolean(mIsHomeSupported);
+        DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
     }
 
     @Override
@@ -232,7 +252,8 @@
                 && mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled
                 && Objects.equals(mDisplayCategories, that.mDisplayCategories)
                 && mRequestedRefreshRate == that.mRequestedRefreshRate
-                && mIsHomeSupported == that.mIsHomeSupported;
+                && mIsHomeSupported == that.mIsHomeSupported
+                && Objects.equals(mDisplayCutout, that.mDisplayCutout);
     }
 
     @Override
@@ -240,7 +261,7 @@
         int hashCode = Objects.hash(
                 mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
                 mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
-                mRequestedRefreshRate, mIsHomeSupported);
+                mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout);
         return hashCode;
     }
 
@@ -260,6 +281,7 @@
                 + " mDisplayCategories=" + mDisplayCategories
                 + " mRequestedRefreshRate=" + mRequestedRefreshRate
                 + " mIsHomeSupported=" + mIsHomeSupported
+                + " mDisplayCutout=" + mDisplayCutout
                 + ")";
     }
 
@@ -276,6 +298,7 @@
         mDisplayCategories = (ArraySet<String>) in.readArraySet(null);
         mRequestedRefreshRate = in.readFloat();
         mIsHomeSupported = in.readBoolean();
+        mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in);
     }
 
     @NonNull
@@ -308,6 +331,7 @@
         private ArraySet<String> mDisplayCategories = new ArraySet<>();
         private float mRequestedRefreshRate = 0.0f;
         private boolean mIsHomeSupported = false;
+        private DisplayCutout mDisplayCutout = null;
 
         /**
          * Creates a new Builder.
@@ -469,6 +493,19 @@
         }
 
         /**
+         * Sets the cutout of this display.
+         *
+         * @hide
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_INSETS)
+        @SystemApi
+        @NonNull
+        public Builder setDisplayCutout(@Nullable DisplayCutout displayCutout) {
+            mDisplayCutout = displayCutout;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDisplayConfig} instance.
          */
         @NonNull
@@ -485,7 +522,8 @@
                     mWindowManagerMirroringEnabled,
                     mDisplayCategories,
                     mRequestedRefreshRate,
-                    mIsHomeSupported);
+                    mIsHomeSupported,
+                    mDisplayCutout);
         }
     }
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 2ded615..903e916 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -699,6 +699,25 @@
     }
 
     /**
+     * Set whether the HAL should ignore display touches.
+     * Only applies to sensors where the HAL is reponsible for handling touches.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouch) {
+        if (mService == null) {
+            Slog.w(TAG, "setIgnoreDisplayTouches: no fingerprint service");
+            return;
+        }
+
+        try {
+            mService.setIgnoreDisplayTouches(requestId, sensorId, ignoreTouch);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Request fingerprint enrollment. This call warms up the fingerprint hardware
      * and starts scanning for fingerprints. Progress will be indicated by callbacks to the
      * {@link EnrollmentCallback} object. It terminates when
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index f701ec3..d84d292 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -120,6 +120,14 @@
     }
 
     /**
+     * Returns if sensor type is ultrasonic Udfps
+     * @return true if sensor is ultrasonic Udfps, false otherwise
+     */
+    public boolean isUltrasonicUdfps() {
+        return sensorType == TYPE_UDFPS_ULTRASONIC;
+    }
+
+    /**
      * Returns if sensor type is side-FPS
      * @return true if sensor is side-fps, false otherwise
      */
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 742fa57..370f097 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -195,6 +195,9 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void onUdfpsUiEvent(int event, long requestId, int sensorId);
 
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches);
+
     // Sets the controller for managing the UDFPS overlay.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void setUdfpsOverlayController(in IUdfpsOverlayController controller);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 6458534..f026997 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -216,3 +216,11 @@
     description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
     bug: "324046728"
 }
+
+flag {
+    name: "get_private_space_settings"
+    namespace: "profile_experiences"
+    description: "Guards a new Private Profile API in LauncherApps"
+    bug: "346294653"
+    is_exported: true
+}
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 555a120..9c2fb7b 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -70,7 +70,7 @@
 
     private final boolean mDefaultKeyboardVibrationEnabled;
 
-    private final boolean mHasFixedKeyboardAmplitude;
+    private final boolean mKeyboardVibrationSettingsSupported;
 
     /** @hide */
     public VibrationConfig(@Nullable Resources resources) {
@@ -89,8 +89,8 @@
                 com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
         mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
                 com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
-        mHasFixedKeyboardAmplitude = loadFloat(resources,
-                com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude, -1) > 0;
+        mKeyboardVibrationSettingsSupported = loadBoolean(resources,
+                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported, false);
 
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -202,11 +202,11 @@
     }
 
     /**
-     * Whether the device has a fixed amplitude for keyboard.
+     * Whether the device support keyboard vibration settings.
      * @hide
      */
-    public boolean hasFixedKeyboardAmplitude() {
-        return mHasFixedKeyboardAmplitude;
+    public boolean isKeyboardVibrationSettingsSupported() {
+        return mKeyboardVibrationSettingsSupported;
     }
 
     /** Get the default vibration intensity for given usage. */
@@ -249,6 +249,7 @@
                 + ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
                 + ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
                 + ", mDefaultKeyboardVibrationEnabled=" + mDefaultKeyboardVibrationEnabled
+                + ", mKeyboardVibrationSettingsSupported=" + mKeyboardVibrationSettingsSupported
                 + "}";
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 94c76929..c954cdb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6178,6 +6178,15 @@
         public static final String POINTER_FILL_STYLE = "pointer_fill_style";
 
         /**
+         * Pointer stroke style, specified by
+         * {@link android.view.PointerIcon.PointerIconVectorStyleStroke} constants.
+         *
+         * @hide
+         */
+        @Readable
+        public static final String POINTER_STROKE_STYLE = "pointer_stroke_style";
+
+        /**
          * Whether lock-to-app will be triggered by long-press on recents.
          * @hide
          */
@@ -6380,6 +6389,7 @@
             PRIVATE_SETTINGS.add(SIP_ASK_ME_EACH_TIME);
             PRIVATE_SETTINGS.add(POINTER_SPEED);
             PRIVATE_SETTINGS.add(POINTER_FILL_STYLE);
+            PRIVATE_SETTINGS.add(POINTER_STROKE_STYLE);
             PRIVATE_SETTINGS.add(POINTER_SCALE);
             PRIVATE_SETTINGS.add(LOCK_TO_APP_ENABLED);
             PRIVATE_SETTINGS.add(EGG_MODE);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 863a99a..e16a6a1 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -2941,7 +2941,7 @@
         long latestEndTime = -1;
 
         // DND turned on by manual rule
-        if (config.manualRule != null) {
+        if (config.isManualActive()) {
             final Uri id = config.manualRule.conditionId;
             if (config.manualRule.enabler != null) {
                 // app triggered manual rule
@@ -2950,7 +2950,7 @@
                     secondaryText = appName;
                 }
             } else {
-                if (id == null) {
+                if (id == null || Uri.EMPTY.equals(id)) {
                     // Do not disturb manually triggered to remain on forever until turned off
                     if (describeForeverCondition) {
                         return context.getString(R.string.zen_mode_forever);
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index c302126..1535145 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -193,6 +193,25 @@
     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END =
             POINTER_ICON_VECTOR_STYLE_FILL_BLUE;
 
+    /** @hide */
+    @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_STROKE_"}, value = {
+            POINTER_ICON_VECTOR_STYLE_STROKE_WHITE,
+            POINTER_ICON_VECTOR_STYLE_STROKE_BLACK,
+            POINTER_ICON_VECTOR_STYLE_STROKE_NONE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PointerIconVectorStyleStroke {}
+
+    /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_WHITE = 0;
+    /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_BLACK = 1;
+    /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_NONE = 2;
+
+    // If adding PointerIconVectorStyleStroke, update END value for {@link SystemSettingsValidators}
+    /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_BEGIN =
+            POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
+    /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_END =
+            POINTER_ICON_VECTOR_STYLE_STROKE_NONE;
+
     /** @hide */ public static final float DEFAULT_POINTER_SCALE = 1f;
     /** @hide */ public static final float LARGE_POINTER_SCALE = 2.5f;
 
@@ -712,6 +731,23 @@
     }
 
     /**
+     * Convert stroke style constant to resource ID.
+     *
+     * @hide
+     */
+    public static int vectorStrokeStyleToResource(@PointerIconVectorStyleStroke int strokeStyle) {
+        return switch (strokeStyle) {
+            case POINTER_ICON_VECTOR_STYLE_STROKE_BLACK ->
+                    com.android.internal.R.style.PointerIconVectorStyleStrokeBlack;
+            case POINTER_ICON_VECTOR_STYLE_STROKE_WHITE ->
+                    com.android.internal.R.style.PointerIconVectorStyleStrokeWhite;
+            case POINTER_ICON_VECTOR_STYLE_STROKE_NONE ->
+                    com.android.internal.R.style.PointerIconVectorStyleStrokeNone;
+            default -> com.android.internal.R.style.PointerIconVectorStyleStrokeWhite;
+        };
+    }
+
+    /**
      * Sets whether drop shadow will draw in the native code.
      *
      * @hide
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 766e02b..4766942 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13859,11 +13859,6 @@
     })
     @ResolvedLayoutDir
     public int getLayoutDirection() {
-        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
-        if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
-            return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
-        }
         return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
                 PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
     }
diff --git a/core/java/android/view/ViewTraversalTracingStrings.java b/core/java/android/view/ViewTraversalTracingStrings.java
index 7dde87b..e95205b 100644
--- a/core/java/android/view/ViewTraversalTracingStrings.java
+++ b/core/java/android/view/ViewTraversalTracingStrings.java
@@ -58,6 +58,6 @@
         out.append(" ");
         out.append(className);
         v.appendId(out);
-        return out.substring(0, Math.min(out.length() - 1, Trace.MAX_SECTION_NAME_LEN - 1));
+        return out.substring(0, Math.min(out.length(), Trace.MAX_SECTION_NAME_LEN));
     }
 }
diff --git a/core/java/android/window/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java
index 85e96c9..67b22f9 100644
--- a/core/java/android/window/TaskFragmentAnimationParams.java
+++ b/core/java/android/window/TaskFragmentAnimationParams.java
@@ -171,7 +171,7 @@
      */
     public boolean hasOverrideAnimation() {
         return mOpenAnimationResId != DEFAULT_ANIMATION_RESOURCES_ID
-                || mChangeAnimationResId != DEFAULT_ANIMATION_BACKGROUND_COLOR
+                || mChangeAnimationResId != DEFAULT_ANIMATION_RESOURCES_ID
                 || mCloseAnimationResId != DEFAULT_ANIMATION_RESOURCES_ID;
     }
 
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 5b99ff9..cc880e1 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -113,4 +113,12 @@
     namespace: "large_screen_experiences_app_compat"
     description: "Enables sysui animation for user aspect ratio button"
     bug: "300357441"
+}
+
+flag {
+  name: "app_compat_ui_framework"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the declarative compat UI framework is enabled"
+  bug: "270361630"
+  is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index d0ab674..1c7acd4 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -127,3 +127,10 @@
     description: "Whether to show developer option for enabling desktop windowing mode"
     bug: "348193756"
 }
+
+flag {
+    name: "enable_desktop_windowing_app_to_web"
+    namespace: "lse_desktop_experience"
+    description: "Whether to enable the app-to-web feature and show the open in browser button in the header menu"
+    bug: "349695493"
+}
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 6a0ec1d..e5ced25 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -126,6 +126,7 @@
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
         optional SettingProto pointer_fill_style = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto pointer_stroke_style = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto pointer_scale = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Pointer pointer = 37;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f61b6bf..9f00d5e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -843,6 +843,7 @@
     <protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" />
     <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
+    <protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
diff --git a/core/res/res/drawable/pointer_alias_vector.xml b/core/res/res/drawable/pointer_alias_vector.xml
index 035a099..60149f1 100644
--- a/core/res/res/drawable/pointer_alias_vector.xml
+++ b/core/res/res/drawable/pointer_alias_vector.xml
@@ -22,10 +22,10 @@
         android:fillColor="#00FFFFFF"
         android:pathData="M14.494 12.779a5.2 5.2 0 0 1-1.771 1.414 5.2 5.2 0 0 1-1.68.489l.81 1.658a1.968 1.968 0 0 0 3.536-1.728zM12.03 8.291l-.81-1.658a1.968 1.968 0 0 0-3.536 1.728l.896 1.833a5.2 5.2 0 0 1 1.77-1.414 5.2 5.2 0 0 1 1.68-.489" />
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="m18.323 13.178-.975-1.995a5.2 5.2 0 0 0-1.704-1.978 5.2 5.2 0 0 0-.517-2.01L14.152 5.2a5.232 5.232 0 0 0-9.401 4.594l.975 1.995a5.2 5.2 0 0 0 1.704 1.978 5.2 5.2 0 0 0 .517 2.01l.975 1.995a5.233 5.233 0 0 0 9.401-4.594m-2.843 6.1a4.23 4.23 0 0 1-5.66-1.944l-.975-1.995a4.2 4.2 0 0 1-.431-1.838l-.001-.276-.234-.146a4.2 4.2 0 0 1-1.555-1.729L5.65 9.355a4.232 4.232 0 1 1 7.604-3.716l.975 1.995c.29.594.428 1.22.431 1.838l.001.276.234.146c.648.405 1.194.99 1.555 1.728l.975 1.995a4.234 4.234 0 0 1-1.945 5.661" />
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M15.313 12.177a3 3 0 0 0-.416-.633l-.459-.534-.353.609a4.2 4.2 0 0 1-1.801 1.675 4.2 4.2 0 0 1-1.977.429l-.704-.02.213.671q.066.208.164.409l.975 1.995a2.967 2.967 0 1 0 5.332-2.606zm-.827 5.066a1.97 1.97 0 0 1-2.632-.904l-.81-1.658a5.2 5.2 0 0 0 1.68-.489 5.2 5.2 0 0 0 1.771-1.414l.896 1.833a1.97 1.97 0 0 1-.905 2.632m-3.697-7.565a4.2 4.2 0 0 1 1.977-.429l.704.02-.213-.671a3 3 0 0 0-.164-.409l-.975-1.995A2.967 2.967 0 1 0 6.785 8.8l.975 1.995q.172.35.416.633l.459.534.353-.609a4.2 4.2 0 0 1 1.801-1.675m-2.21.516-.895-1.833a1.968 1.968 0 1 1 3.536-1.728l.81 1.658a5.2 5.2 0 0 0-1.68.489 5.2 5.2 0 0 0-1.771 1.414m3.151 1.965a3 3 0 0 0 1.02-.818l.755-.95-1.205.142a2.97 2.97 0 0 0-1.975 1.1l-.755.95 1.205-.142c.324-.039.646-.132.955-.282" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_all_scroll_vector.xml b/core/res/res/drawable/pointer_all_scroll_vector.xml
index 45ad98c..41314ed 100644
--- a/core/res/res/drawable/pointer_all_scroll_vector.xml
+++ b/core/res/res/drawable/pointer_all_scroll_vector.xml
@@ -24,5 +24,5 @@
         android:fillColor="?attr/pointerIconVectorFill"/>
     <path
         android:pathData="M12 4c.36 0 .72.18.93.54l1.75 3.06c.41.71-.1 1.6-.92 1.6h-.82v1.86h1.86v-.84a1.07 1.07 0 0 1 1.6-.92l3.06 1.75c.72.41.72 1.44 0 1.85l-3.06 1.76a1.07 1.07 0 0 1-1.6-.92v-.8h-1.86v1.87h.82c.82 0 1.33.88.92 1.6l-1.75 3.06a1.07 1.07 0 0 1-1.85 0L9.32 16.4c-.4-.7.1-1.6.93-1.6h.81v-1.86H9.2v.8a1.07 1.07 0 0 1-1.6.92L4.54 12.9a1.06 1.06 0 0 1 0-1.85L7.6 9.3a1.07 1.07 0 0 1 1.6.92v.85h1.86V9.2h-.82c-.81 0-1.33-.89-.92-1.6l1.76-3.06c.2-.36.56-.54.92-.54m0-1c-.74 0-1.41.39-1.79 1.04L8.45 7.1c-.18.33-.28.7-.27 1.05h-.05c-.36 0-.71.1-1.03.28l-3.06 1.76a2.05 2.05 0 0 0 0 3.58l3.06 1.75c.32.18.67.28 1.03.28h.05c-.01.38.08.76.28 1.1l1.75 3.07c.38.65 1.05 1.03 1.8 1.03s1.41-.38 1.78-1.03l1.76-3.07c.2-.34.3-.72.28-1.1h.04c.36 0 .71-.1 1.03-.28l3.06-1.75a2.07 2.07 0 0 0 0-3.58L16.9 8.43a2.07 2.07 0 0 0-1.03-.28h-.04c0-.36-.09-.72-.28-1.05L13.8 4.04A2.04 2.04 0 0 0 12 3z"
-        android:fillColor="#FFFFFF"/>
+        android:fillColor="?attr/pointerIconVectorStroke"/>
 </vector>
diff --git a/core/res/res/drawable/pointer_arrow_vector.xml b/core/res/res/drawable/pointer_arrow_vector.xml
index 2614170..a0bd5b6 100644
--- a/core/res/res/drawable/pointer_arrow_vector.xml
+++ b/core/res/res/drawable/pointer_arrow_vector.xml
@@ -24,5 +24,5 @@
         android:fillColor="?attr/pointerIconVectorFill"/>
     <path
         android:pathData="M16.94 10.38 7.37 3.22a2.77 2.77 0 0 0-2.93-.27 2.75 2.75 0 0 0-1.55 2.51l.01 11.95a2.78 2.78 0 0 0 2.82 2.8c.77 0 1.5-.32 2.03-.9l2.97-3.19a.8.8 0 0 1 .5-.25l4.34-.46a2.76 2.76 0 0 0 2.4-2.05 2.8 2.8 0 0 0-1.02-2.98zM17 13.1a1.77 1.77 0 0 1-1.55 1.31l-4.33.47a1.8 1.8 0 0 0-1.13.56l-2.97 3.2c-.4.42-.86.57-1.3.57-.24 0-.48-.05-.68-.13a1.77 1.77 0 0 1-1.14-1.67V5.46a1.81 1.81 0 0 1 1.8-1.8c.38 0 .75.11 1.07.36l9.57 7.16c.72.54.81 1.35.66 1.92z"
-        android:fillColor="#FFFFFF"/>
+        android:fillColor="?attr/pointerIconVectorStroke"/>
 </vector>
diff --git a/core/res/res/drawable/pointer_cell_vector.xml b/core/res/res/drawable/pointer_cell_vector.xml
index cead1c4..9ad64e27 100644
--- a/core/res/res/drawable/pointer_cell_vector.xml
+++ b/core/res/res/drawable/pointer_cell_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M19 9.667h-4.668V5a2 2 0 0 0-2-2h-.667a2 2 0 0 0-2 2v4.667H5a2 2 0 0 0-2 2v.667a2 2 0 0 0 2 2h4.665V19a2 2 0 0 0 2 2h.667a2 2 0 0 0 2-2v-4.666H19a2 2 0 0 0 2-2v-.667a2 2 0 0 0-2-2m1 2.667a1 1 0 0 1-1 1h-5.668V19a1 1 0 0 1-1 1h-.667a1 1 0 0 1-1-1v-5.666H5a1 1 0 0 1-1-1v-.667a1 1 0 0 1 1-1h5.665V5a1 1 0 0 1 1-1h.667a1 1 0 0 1 1 1v5.667H19a1 1 0 0 1 1 1z" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_context_menu_vector.xml b/core/res/res/drawable/pointer_context_menu_vector.xml
index fb2af43..33b1ace 100644
--- a/core/res/res/drawable/pointer_context_menu_vector.xml
+++ b/core/res/res/drawable/pointer_context_menu_vector.xml
@@ -19,11 +19,11 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <group>
-        <path android:fillColor="#FFFFFF" android:pathData="M19.475 2.604h-2.66c-.842 0-1.527.685-1.527 1.527v2.66c0 .842.685 1.527 1.527 1.527h2.66c.842 0 1.527-.685 1.527-1.527v-2.66c0-.842-.685-1.527-1.527-1.527m.67 4.187c0 .37-.3.67-.67.67h-2.66a.67.67 0 0 1-.67-.67v-2.66c0-.37.3-.67.67-.67h2.66c.37 0 .67.3.67.67z" />
-        <path android:fillColor="#FFFFFF" android:pathData="M19.175 4.17h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .886h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .868h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="M19.475 2.604h-2.66c-.842 0-1.527.685-1.527 1.527v2.66c0 .842.685 1.527 1.527 1.527h2.66c.842 0 1.527-.685 1.527-1.527v-2.66c0-.842-.685-1.527-1.527-1.527m.67 4.187c0 .37-.3.67-.67.67h-2.66a.67.67 0 0 1-.67-.67v-2.66c0-.37.3-.67.67-.67h2.66c.37 0 .67.3.67.67z" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="M19.175 4.17h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .886h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .868h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6" />
     </group>
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M16.938 10.38 7.372 3.216a2.77 2.77 0 0 0-2.931-.262A2.75 2.75 0 0 0 2.894 5.46l.009 11.951a2.785 2.785 0 0 0 1.776 2.604c.33.129.691.197 1.044.197a2.75 2.75 0 0 0 2.031-.897l2.969-3.193a.8.8 0 0 1 .5-.25l4.336-.467c1.397-.15 2.157-1.153 2.401-2.041a2.785 2.785 0 0 0-1.022-2.984m.058 2.718c-.157.571-.645 1.216-1.544 1.312l-4.335.467a1.8 1.8 0 0 0-1.126.563l-2.97 3.193a1.74 1.74 0 0 1-1.298.578 1.9 1.9 0 0 1-.678-.128c-.551-.217-1.141-.771-1.142-1.674l-.009-11.95c0-.697.371-1.299.994-1.611.262-.131.538-.196.813-.196.377 0 .75.123 1.072.365l9.566 7.163c.723.542.814 1.346.657 1.918" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_copy_vector.xml b/core/res/res/drawable/pointer_copy_vector.xml
index 3f13868..8ac8c21 100644
--- a/core/res/res/drawable/pointer_copy_vector.xml
+++ b/core/res/res/drawable/pointer_copy_vector.xml
@@ -23,7 +23,7 @@
         <path android:fillColor="?attr/pointerIconVectorFillInverse" android:pathData="M19.299 6H18V4.7a.5.5 0 0 0-1 0V6h-1.301a.5.5 0 0 0 0 1H17v1.3a.5.5 0 0 0 1 0V7h1.299a.5.5 0 0 0 0-1" />
     </group>
     <path
-        android:fillColor="#000000"
+        android:fillColor="?attr/pointerIconVectorStrokeInverse"
         android:pathData="M18.485 10.884v1.739q.013.189.013.38c0 3.045-2.518 5.514-5.563 5.514a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022q.22-.018.446-.018c.062 0 .123.007.185.009A4.4 4.4 0 0 1 13 6.5c0-.378.061-.739.149-1.09a1.97 1.97 0 0 0-1.659-.926c-.903 0-1.658.603-1.906 1.425a1.997 1.997 0 0 0-3.091 1.674v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514q-.001-.192-.013-.381v-2.108c-.316.156-.645.29-.999.37" />
     <path
         android:fillColor="#1FA54A"
diff --git a/core/res/res/drawable/pointer_crosshair_vector.xml b/core/res/res/drawable/pointer_crosshair_vector.xml
index 8a50d1b..4fba080 100644
--- a/core/res/res/drawable/pointer_crosshair_vector.xml
+++ b/core/res/res/drawable/pointer_crosshair_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M19.25 10.25h-5.5v-5.5a1.75 1.75 0 0 0-3.5 0v5.5h-5.5a1.75 1.75 0 0 0 0 3.5h5.5v5.5a1.75 1.75 0 0 0 3.5 0v-5.5h5.5a1.75 1.75 0 0 0 0-3.5m0 2.5h-6.5v6.5a.75.75 0 0 1-1.5 0v-6.5h-6.5a.75.75 0 0 1 0-1.5h6.5v-6.5a.75.75 0 0 1 1.5 0v6.5h6.5a.75.75 0 0 1 0 1.5" />
     <path
         android:fillType="evenOdd"
diff --git a/core/res/res/drawable/pointer_grab_vector.xml b/core/res/res/drawable/pointer_grab_vector.xml
index 48c01ce..d9e1f18 100644
--- a/core/res/res/drawable/pointer_grab_vector.xml
+++ b/core/res/res/drawable/pointer_grab_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#000000"
+        android:fillColor="?attr/pointerIconVectorStrokeInverse"
         android:pathData="M20.442 7.562a2 2 0 0 0-2-2c-.366 0-.705.106-1 .277V4.686a2 2 0 0 0-2-2 2 2 0 0 0-1.004.279 1.995 1.995 0 0 0-3.986-.06 2 2 0 0 0-1.006-.28 2 2 0 0 0-2 2v6.501l-.247-.253a2.216 2.216 0 0 0-3.178 0 2.286 2.286 0 0 0 0 3.186l5.106 5.224q.063.061.131.118l-.001.001a6.58 6.58 0 0 0 4.624 1.901c3.587 0 6.565-2.906 6.565-6.516q0-.105-.004-.21m-6.561 5.727a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-5.106-5.224a1.286 1.286 0 0 1 0-1.788 1.215 1.215 0 0 1 1.747 0l1.962 2.008V4.625a1 1 0 0 1 2 0v5.833q.463-.362.996-.623V3a1 1 0 0 1 2 0v6.29a6 6 0 0 1 1 .011V4.686a1 1 0 0 1 2 0v5.21c.357.185.693.408 1 .663V7.562a1 1 0 0 1 2 0v7.019h.001-.001q.004.104.004.207c.001 3.046-2.518 5.516-5.564 5.516" />
     <path
         android:fillColor="?attr/pointerIconVectorFillInverse"
diff --git a/core/res/res/drawable/pointer_grabbing_vector.xml b/core/res/res/drawable/pointer_grabbing_vector.xml
index ad9f86c..7f52fc5 100644
--- a/core/res/res/drawable/pointer_grabbing_vector.xml
+++ b/core/res/res/drawable/pointer_grabbing_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#000000"
+        android:fillColor="?attr/pointerIconVectorStrokeInverse"
         android:pathData="M19.485 12.622V8.508a2 2 0 0 0-3.12-1.657 1.993 1.993 0 0 0-2.99-1.006 1.99 1.99 0 0 0-1.886-1.361c-.903 0-1.658.603-1.906 1.425a2 2 0 0 0-3.09 1.674v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514a5 5 0 0 0-.012-.381m-6.55 5.895a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022a5.5 5.5 0 0 1 .996.009v-.007a1 1 0 0 1 2 0v.599q.537.277 1 .66v-.259a1 1 0 0 1 2 0v4.115q.013.189.013.38c-.001 3.045-2.518 5.514-5.564 5.514" />
     <path
         android:fillColor="?attr/pointerIconVectorFillInverse"
diff --git a/core/res/res/drawable/pointer_hand_vector.xml b/core/res/res/drawable/pointer_hand_vector.xml
index a06dc08..115b53b 100644
--- a/core/res/res/drawable/pointer_hand_vector.xml
+++ b/core/res/res/drawable/pointer_hand_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#000000"
+        android:fillColor="?attr/pointerIconVectorStrokeInverse"
         android:pathData="M20.492 15.197v-4.198A1.995 1.995 0 0 0 18.5 9.001c-.413 0-.797.126-1.115.342a1.99 1.99 0 0 0-1.873-1.341c-.411 0-.792.125-1.109.339a1.99 1.99 0 0 0-1.879-1.361c-.363 0-.699.105-.992.275V3.998A1.99 1.99 0 0 0 9.542 2c-1.1 0-1.992.895-1.992 1.998v7.831l-.242-.249a2.2 2.2 0 0 0-3.164 0 2.29 2.29 0 0 0 0 3.183l5.084 5.219q.063.061.13.118l-.001.001A6.54 6.54 0 0 0 13.963 22c3.572 0 6.537-2.903 6.537-6.509q0-.148-.008-.294m-6.529 5.804a5.55 5.55 0 0 1-3.906-1.611 1 1 0 0 1-.117-.106l-5.084-5.219a1.286 1.286 0 0 1 0-1.786 1.21 1.21 0 0 1 1.74 0l1.95 2.002V3.998c0-.552.446-.999.996-.999s.996.447.996.999v7.17l.011-.007a.495.495 0 0 0 .989-.037V8.939a.992.992 0 0 1 1.984.039v.796l-.007 1.386a.5.5 0 0 0 .495.502h.003a.5.5 0 0 0 .498-.497l.006-1.157h.001V10a.997.997 0 1 1 1.991 0v.601l.004.003v1.02q.001.107.042.199a.5.5 0 0 0 .153.187l.031.021a.5.5 0 0 0 .231.083c.014.001.026.008.04.008a.5.5 0 0 0 .498-.5v-.642a.996.996 0 0 1 .993-.98c.55 0 .996.447.996.999v4.199a6 6 0 0 1 .008.293c-.001 3.043-2.509 5.51-5.542 5.51" />
     <path
         android:fillColor="?attr/pointerIconVectorFillInverse"
diff --git a/core/res/res/drawable/pointer_handwriting_vector.xml b/core/res/res/drawable/pointer_handwriting_vector.xml
index 8497592..6f62aba 100644
--- a/core/res/res/drawable/pointer_handwriting_vector.xml
+++ b/core/res/res/drawable/pointer_handwriting_vector.xml
@@ -19,8 +19,8 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <group>
-        <path android:fillColor="#FFFFFF" android:pathData="M20.12 6.8 18.7 5.38c-.57-.57-1.32-.88-2.12-.88s-1.56.31-2.13.88l-7.16 7.16-.29.29V5c0-1.1-.9-2-2-2s-2 .9-2 2v14c0 1.1.9 2 2 2s2-.9 2-2v-.5h5.67l.29-.29 7.16-7.16c.57-.57.88-1.32.88-2.12s-.31-1.57-.88-2.13M6 19c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55.45-1 1-1s1 .45 1 1zm13.41-8.66-7.16 7.16H8v-4.25l7.16-7.16c.39-.39.9-.59 1.41-.59h.01c.51 0 1.02.2 1.41.59l1.42 1.42c.78.78.78 2.05 0 2.83" />
-        <path android:fillColor="#FFFFFF" android:pathData="m16.431 7.64-6.29 6.29 1.43 1.43 6.29-6.29-1.42-1.43z" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="M20.12 6.8 18.7 5.38c-.57-.57-1.32-.88-2.12-.88s-1.56.31-2.13.88l-7.16 7.16-.29.29V5c0-1.1-.9-2-2-2s-2 .9-2 2v14c0 1.1.9 2 2 2s2-.9 2-2v-.5h5.67l.29-.29 7.16-7.16c.57-.57.88-1.32.88-2.12s-.31-1.57-.88-2.13M6 19c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55.45-1 1-1s1 .45 1 1zm13.41-8.66-7.16 7.16H8v-4.25l7.16-7.16c.39-.39.9-.59 1.41-.59h.01c.51 0 1.02.2 1.41.59l1.42 1.42c.78.78.78 2.05 0 2.83" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="m16.431 7.64-6.29 6.29 1.43 1.43 6.29-6.29-1.42-1.43z" />
     </group>
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_help_vector.xml b/core/res/res/drawable/pointer_help_vector.xml
index 07970fb..63cf287 100644
--- a/core/res/res/drawable/pointer_help_vector.xml
+++ b/core/res/res/drawable/pointer_help_vector.xml
@@ -22,7 +22,7 @@
         android:fillColor="?attr/pointerIconVectorFill"
         android:pathData="M16.339 11.18 6.773 4.017a1.78 1.78 0 0 0-1.072-.365c-.274 0-.551.065-.813.196a1.77 1.77 0 0 0-.994 1.611l.009 11.951c0 .903.59 1.457 1.142 1.674.2.078.433.128.678.128.434 0 .906-.155 1.298-.578l2.97-3.193a1.8 1.8 0 0 1 1.126-.563l4.335-.467c.899-.097 1.387-.741 1.544-1.312.157-.573.066-1.377-.657-1.919" />
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M16.94 10.38 7.37 3.22a2.77 2.77 0 0 0-2.93-.27A2.75 2.75 0 0 0 2.9 5.46l.01 11.95a2.79 2.79 0 0 0 2.82 2.8c.78 0 1.5-.32 2.03-.9l2.97-3.19a.8.8 0 0 1 .5-.25l4.34-.46a2.76 2.76 0 0 0 2.4-2.05 2.8 2.8 0 0 0-1.02-2.98zM17 13.1a1.77 1.77 0 0 1-1.55 1.31l-4.33.47a1.8 1.8 0 0 0-1.13.56l-2.97 3.2c-.4.42-.86.57-1.3.57-.24 0-.48-.05-.68-.13a1.77 1.77 0 0 1-1.14-1.67V5.46a1.81 1.81 0 0 1 1.8-1.8c.38 0 .75.11 1.07.36l9.57 7.16c.72.54.81 1.35.66 1.92zm2.64-10.83a2.5 2.5 0 0 0-1.84-.72 3 3 0 0 0-2.83 1.93l-.39.94.96.37.86.32.12.05-.02.03c-.22.4-.3.82-.3 1.33v.94a1.56 1.56 0 0 0 .4 1.47 1.54 1.54 0 0 0 2.24.01 1.55 1.55 0 0 0 .28-1.84v-.52c0-.1.02-.17.03-.25l.16-.15c.32-.25.6-.56.78-.93.18-.37.26-.76.26-1.16 0-.68-.21-1.32-.7-1.82zm-1.5 5.96a.55.55 0 0 1-.82 0 .56.56 0 0 1-.17-.4c0-.16.06-.3.17-.4a.55.55 0 0 1 .41-.18c.15 0 .28.06.4.17a.55.55 0 0 1 0 .81zm1.05-3.42c-.1.22-.28.42-.52.6-.26.22-.42.42-.47.6-.05.18-.08.37-.08.57l-.93-.06c0-.38.07-.62.19-.86.13-.24.3-.46.54-.66.17-.13.3-.28.4-.43s.14-.3.14-.46c0-.2-.08-.37-.22-.5s-.31-.17-.52-.17c-.2 0-.39.06-.56.18-.17.13-.3.31-.4.56l-.87-.33a2.03 2.03 0 0 1 1.91-1.3c.48 0 .86.14 1.13.42.28.28.41.65.41 1.12 0 .26-.05.5-.15.72z" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml
index 32c56b6..7aa6571 100644
--- a/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="m19.963 10.185-3.065-1.758c-1.327-.761-2.96.14-3.072 1.633h-3.651c-.113-1.492-1.746-2.394-3.072-1.633l-3.065 1.758c-1.383.793-1.383 2.786 0 3.579l3.065 1.758c1.311.752 2.918-.12 3.065-1.581h3.666c.147 1.46 1.754 2.333 3.065 1.581l3.065-1.758c1.382-.793 1.382-2.786-.001-3.579m-.498 2.712L16.4 14.655a1.065 1.065 0 0 1-1.596-.922v-.791H9.195v.791c0 .818-.886 1.33-1.596.922l-3.065-1.758a1.063 1.063 0 0 1 0-1.845l3.065-1.758a1.065 1.065 0 0 1 1.596.922v.843h5.609v-.843c0-.818.886-1.33 1.596-.922l3.065 1.758a1.063 1.063 0 0 1 0 1.845" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_nodrop_vector.xml b/core/res/res/drawable/pointer_nodrop_vector.xml
index 6108e96..7cef01a 100644
--- a/core/res/res/drawable/pointer_nodrop_vector.xml
+++ b/core/res/res/drawable/pointer_nodrop_vector.xml
@@ -23,7 +23,7 @@
         <path android:fillColor="?attr/pointerIconVectorFillInverse" android:pathData="M17.5 4c-.493 0-.95.148-1.337.395l3.442 3.442C19.852 7.45 20 6.993 20 6.5 20 5.121 18.879 4 17.5 4M15 6.5C15 7.879 16.121 9 17.5 9c.525 0 1.011-.164 1.413-.441l-3.472-3.472A2.5 2.5 0 0 0 15 6.5" />
     </group>
     <path
-        android:fillColor="#000000"
+        android:fillColor="?attr/pointerIconVectorStrokeInverse"
         android:pathData="M18.485 10.932v1.69q.013.188.013.38c0 3.045-2.518 5.514-5.563 5.514a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022q.22-.018.446-.018c.046 0 .09.006.135.007a4.5 4.5 0 0 1-.117-.995c0-.399.068-.779.165-1.148a1.97 1.97 0 0 0-1.629-.867c-.903 0-1.658.603-1.906 1.425a2 2 0 0 0-1.091-.327 2 2 0 0 0-2 2v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514q-.001-.192-.013-.381v-2.056a4.5 4.5 0 0 1-.999.366" />
     <path
         android:fillColor="#B22A25"
diff --git a/core/res/res/drawable/pointer_text_vector.xml b/core/res/res/drawable/pointer_text_vector.xml
index a147273..2fa1332 100644
--- a/core/res/res/drawable/pointer_text_vector.xml
+++ b/core/res/res/drawable/pointer_text_vector.xml
@@ -22,6 +22,6 @@
         android:fillColor="?attr/pointerIconVectorFill"
         android:pathData="M12 3c-.551 0-1 .448-1 1v14a1.001 1.001 0 0 0 2 0V4c0-.552-.449-1-1-1" />
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M12 2c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2s2-.897 2-2V4c0-1.103-.897-2-2-2m1 16a1.001 1.001 0 0 1-2 0V4a1.001 1.001 0 0 1 2 0z" />
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml
index 7f95207..cd4e5e7 100644
--- a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="m18.896 16.365-.924-3.41c-.398-1.467-2.169-1.985-3.305-1.035L12.08 9.333c.952-1.136.434-2.908-1.034-3.306l-3.41-.924c-1.539-.416-2.948.993-2.532 2.532l.924 3.41c.398 1.468 2.17 1.986 3.306 1.034l2.586 2.586c-.953 1.136-.435 2.91 1.033 3.307l3.41.924c1.54.417 2.949-.992 2.533-2.531m-2.27 1.566-3.41-.924a1.065 1.065 0 0 1-.476-1.781l.579-.579-3.966-3.966-.579.579a1.066 1.066 0 0 1-1.781-.476L6.07 7.373a1.063 1.063 0 0 1 1.304-1.304l3.41.924a1.065 1.065 0 0 1 .476 1.781l-.578.578 3.966 3.966.577-.577a1.066 1.066 0 0 1 1.781.477l.924 3.41a1.062 1.062 0 0 1-1.304 1.303" />
     <path
         android:fillType="evenOdd"
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml
index 8a33715..3736290 100644
--- a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="m16.365 5.104-3.41.924c-1.468.398-1.986 2.171-1.033 3.307l-2.586 2.586c-1.136-.952-2.909-.434-3.306 1.034l-.924 3.41c-.417 1.539.992 2.948 2.531 2.531l3.41-.924c1.468-.398 1.986-2.17 1.034-3.306l2.587-2.587c1.136.951 2.908.432 3.305-1.035l.924-3.41c.415-1.538-.994-2.947-2.532-2.53m1.565 2.269-.924 3.41a1.065 1.065 0 0 1-1.781.476l-.577-.577-3.966 3.966.578.578a1.066 1.066 0 0 1-.476 1.781l-3.41.924a1.063 1.063 0 0 1-1.304-1.304l.924-3.41a1.066 1.066 0 0 1 1.781-.477l.578.578 3.966-3.966-.579-.579a1.066 1.066 0 0 1 .476-1.781l3.41-.924a1.063 1.063 0 0 1 1.304 1.305" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml b/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml
index 889372c..ff99f41 100644
--- a/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml
@@ -19,7 +19,7 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M13.945 13.829V10.17c1.476-.131 2.363-1.75 1.606-3.069l-1.758-3.065c-.793-1.383-2.786-1.383-3.579 0L8.455 7.102c-.757 1.319.131 2.939 1.607 3.069v3.658c-1.477.13-2.364 1.75-1.607 3.069l1.758 3.065c.793 1.383 2.786 1.383 3.579 0l1.758-3.065c.758-1.319-.129-2.938-1.605-3.069m.739 2.572-1.758 3.065a1.063 1.063 0 0 1-1.845 0l-1.758-3.065a1.065 1.065 0 0 1 .922-1.596h.818v-5.61h-.818c-.818 0-1.33-.886-.922-1.596l1.758-3.065a1.063 1.063 0 0 1 1.845 0l1.758 3.065a1.065 1.065 0 0 1-.922 1.596h-.817v5.609h.817c.817.001 1.329.886.922 1.597" />
     <path
         android:fillColor="?attr/pointerIconVectorFill"
diff --git a/core/res/res/drawable/pointer_vertical_text_vector.xml b/core/res/res/drawable/pointer_vertical_text_vector.xml
index 9238f94..d610d04 100644
--- a/core/res/res/drawable/pointer_vertical_text_vector.xml
+++ b/core/res/res/drawable/pointer_vertical_text_vector.xml
@@ -22,6 +22,6 @@
         android:fillColor="?attr/pointerIconVectorFill"
         android:pathData="M19 11H5a1 1 0 0 0 0 2h14a1 1 0 0 0 0-2" />
     <path
-        android:fillColor="#FFFFFF"
+        android:fillColor="?attr/pointerIconVectorStroke"
         android:pathData="M19 10H5c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2m0 3H5a1 1 0 0 1 0-2h14a1 1 0 0 1 0 2" />
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_zoom_in_vector.xml b/core/res/res/drawable/pointer_zoom_in_vector.xml
index a7f56c2..c4aa95b 100644
--- a/core/res/res/drawable/pointer_zoom_in_vector.xml
+++ b/core/res/res/drawable/pointer_zoom_in_vector.xml
@@ -19,8 +19,8 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <group>
-        <path android:fillColor="#FFFFFF" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" />
-        <path android:fillColor="#FFFFFF" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-2v2a.5.5 0 0 1-1 0v-2h-2a.5.5 0 0 1 0-1h2V7a.5.5 0 0 1 1 0v2h2a.5.5 0 0 1 0 1" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-2v2a.5.5 0 0 1-1 0v-2h-2a.5.5 0 0 1 0-1h2V7a.5.5 0 0 1 1 0v2h2a.5.5 0 0 1 0 1" />
     </group>
     <group>
         <path android:fillColor="?attr/pointerIconVectorFill" android:pathData="m19.736 18.003-4.194-4.22a6.547 6.547 0 1 0-1.382 1.226l4.268 4.294a.923.923 0 0 0 1.308-1.3m-9.186-3.91A4.546 4.546 0 1 1 10.549 5a4.546 4.546 0 0 1 .001 9.093" />
diff --git a/core/res/res/drawable/pointer_zoom_out_vector.xml b/core/res/res/drawable/pointer_zoom_out_vector.xml
index e46b978..16c4520 100644
--- a/core/res/res/drawable/pointer_zoom_out_vector.xml
+++ b/core/res/res/drawable/pointer_zoom_out_vector.xml
@@ -19,8 +19,8 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <group>
-        <path android:fillColor="#FFFFFF" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" />
-        <path android:fillColor="#FFFFFF" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" />
+        <path android:fillColor="?attr/pointerIconVectorStroke" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1" />
     </group>
     <group>
         <path android:fillColor="?attr/pointerIconVectorFill" android:pathData="m19.736 18.003-4.194-4.22a6.547 6.547 0 1 0-1.382 1.226l4.268 4.294a.923.923 0 0 0 1.308-1.3m-9.186-3.91A4.546 4.546 0 1 1 10.549 5a4.546 4.546 0 0 1 .001 9.093" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ea993ee..4892f59 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10042,6 +10042,8 @@
     <declare-styleable name="PointerIconVectorTheme">
         <attr name="pointerIconVectorFill" format="color" />
         <attr name="pointerIconVectorFillInverse" format="color" />
+        <attr name="pointerIconVectorStroke" format="color" />
+        <attr name="pointerIconVectorStrokeInverse" format="color" />
     </declare-styleable>
 
     <declare-styleable name="Storage">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 335b740..ca80e22 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4136,6 +4136,9 @@
     <!-- The default value for keyboard vibration toggle in settings. -->
     <bool name="config_defaultKeyboardVibrationEnabled">true</bool>
 
+    <!-- Indicating if keyboard vibration settings supported or not. -->
+    <bool name="config_keyboardVibrationSettingsSupported">false</bool>
+
     <!-- If the device should still vibrate even in low power mode, for certain priority vibrations
      (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
     <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 61c7a8c..cdd8557 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -393,6 +393,12 @@
     <bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
     <java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
 
+    <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if
+     existing cellular networks already supports IWLAN.
+     -->
+    <bool name="force_iwlan_mms_feature_enabled">false</bool>
+    <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" />
+
     <!-- The time duration in millis after which Telephony will abort the last message datagram
      sending requests. Telephony starts a timer when receiving a last message datagram sending
      request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index bd93aa9..77d2e87 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -14,6 +14,11 @@
      limitations under the License.
 -->
 
+<!--
+     ICU Version: 75.1
+     CLDR Data Version: 45
+-->
+
 <resources>
 
     <string-array translatable="false" name="supported_locales">
@@ -88,6 +93,11 @@
         <item>bem-ZM</item> <!-- Bemba (Zambia) -->
         <item>bez-TZ</item> <!-- Bena (Tanzania) -->
         <item>bg-BG</item> <!-- Bulgarian (Bulgaria) -->
+        <item>bgc-IN</item> <!-- Haryanvi (India) -->
+        <item>bgc-IN-u-nu-latn</item> <!-- Haryanvi (India, Western Digits) -->
+        <item>bho-IN</item> <!-- Bhojpuri (India) -->
+        <item>bho-IN-u-nu-latn</item> <!-- Bhojpuri (India, Western Digits) -->
+        <item>blo-BJ</item> <!-- Anii (Benin) -->
         <item>bm-ML</item> <!-- Bambara (Mali) -->
         <item>bn-BD</item> <!-- Bangla (Bangladesh) -->
         <item>bn-BD-u-nu-latn</item> <!-- Bangla (Bangladesh, Western Digits) -->
@@ -108,6 +118,7 @@
         <item>cgg-UG</item> <!-- Chiga (Uganda) -->
         <item>chr-US</item> <!-- Cherokee (United States) -->
         <item>cs-CZ</item> <!-- Czech (Czechia) -->
+        <item>csw-CA</item> <!-- Swampy Cree (Canada) -->
         <item>cv-RU</item> <!-- Chuvash (Russia) -->
         <item>cy-GB</item> <!-- Welsh (United Kingdom) -->
         <item>da-DK</item> <!-- Danish (Denmark) -->
@@ -170,6 +181,7 @@
         <item>en-GU</item> <!-- English (Guam) -->
         <item>en-GY</item> <!-- English (Guyana) -->
         <item>en-HK</item> <!-- English (Hong Kong) -->
+        <item>en-ID</item> <!-- English (Indonesia) -->
         <item>en-IE</item> <!-- English (Ireland) -->
         <item>en-IL</item> <!-- English (Israel) -->
         <item>en-IM</item> <!-- English (Isle of Man) -->
@@ -237,6 +249,7 @@
         <item>en-ZA</item> <!-- English (South Africa) -->
         <item>en-ZM</item> <!-- English (Zambia) -->
         <item>en-ZW</item> <!-- English (Zimbabwe) -->
+        <item>eo-001</item> <!-- Esperanto (world) -->
         <item>es-AR</item> <!-- Spanish (Argentina) -->
         <item>es-BO</item> <!-- Spanish (Bolivia) -->
         <item>es-BR</item> <!-- Spanish (Brazil) -->
@@ -381,6 +394,7 @@
         <item>hu-HU</item> <!-- Hungarian (Hungary) -->
         <item>hy-AM</item> <!-- Armenian (Armenia) -->
         <item>ia-001</item> <!-- Interlingua (world) -->
+        <item>ie-EE</item> <!-- Interlingue (Estonia) -->
         <item>ig-NG</item> <!-- Igbo (Nigeria) -->
         <item>ii-CN</item> <!-- Sichuan Yi (China) -->
         <item>in-ID</item> <!-- Indonesian (Indonesia) -->
@@ -408,6 +422,7 @@
         <item>kln-KE</item> <!-- Kalenjin (Kenya) -->
         <item>km-KH</item> <!-- Khmer (Cambodia) -->
         <item>kn-IN</item> <!-- Kannada (India) -->
+        <item>ko-CN</item> <!-- Korean (China) -->
         <item>ko-KP</item> <!-- Korean (North Korea) -->
         <item>ko-KR</item> <!-- Korean (South Korea) -->
         <item>kok-IN</item> <!-- Konkani (India) -->
@@ -417,12 +432,19 @@
         <item>ksb-TZ</item> <!-- Shambala (Tanzania) -->
         <item>ksf-CM</item> <!-- Bafia (Cameroon) -->
         <item>ksh-DE</item> <!-- Colognian (Germany) -->
+        <item>ku-TR</item> <!-- Kurdish (Türkiye) -->
         <item>kw-GB</item> <!-- Cornish (United Kingdom) -->
+        <item>kxv-Deva-IN</item> <!-- Kuvi (Devanagari, India) -->
+        <item>kxv-Latn-IN</item> <!-- Kuvi (Latin, India) -->
+        <item>kxv-Orya-IN</item> <!-- Kuvi (Odia, India) -->
+        <item>kxv-Telu-IN</item> <!-- Kuvi (Telugu, India) -->
         <item>ky-KG</item> <!-- Kyrgyz (Kyrgyzstan) -->
         <item>lag-TZ</item> <!-- Langi (Tanzania) -->
         <item>lb-LU</item> <!-- Luxembourgish (Luxembourg) -->
         <item>lg-UG</item> <!-- Ganda (Uganda) -->
+        <item>lij-IT</item> <!-- Ligurian (Italy) -->
         <item>lkt-US</item> <!-- Lakota (United States) -->
+        <item>lmo-IT</item> <!-- Lombard (Italy) -->
         <item>ln-AO</item> <!-- Lingala (Angola) -->
         <item>ln-CD</item> <!-- Lingala (Congo - Kinshasa) -->
         <item>ln-CF</item> <!-- Lingala (Central African Republic) -->
@@ -462,6 +484,8 @@
         <item>nb-NO</item> <!-- Norwegian Bokmål (Norway) -->
         <item>nb-SJ</item> <!-- Norwegian Bokmål (Svalbard & Jan Mayen) -->
         <item>nd-ZW</item> <!-- North Ndebele (Zimbabwe) -->
+        <item>nds-DE</item> <!-- Low German (Germany) -->
+        <item>nds-NL</item> <!-- Low German (Netherlands) -->
         <item>ne-IN</item> <!-- Nepali (India) -->
         <item>ne-IN-u-nu-latn</item> <!-- Nepali (India, Western Digits) -->
         <item>ne-NP</item> <!-- Nepali (Nepal) -->
@@ -475,8 +499,12 @@
         <item>nl-SX</item> <!-- Dutch (Sint Maarten) -->
         <item>nn-NO</item> <!-- Norwegian Nynorsk (Norway) -->
         <item>nnh-CM</item> <!-- Ngiemboon (Cameroon) -->
+        <item>nqo-GN</item> <!-- N’Ko (Guinea) -->
+        <item>nqo-GN-u-nu-latn</item> <!-- N’Ko (Guinea, Western Digits) -->
         <item>nus-SS</item> <!-- Nuer (South Sudan) -->
         <item>nyn-UG</item> <!-- Nyankole (Uganda) -->
+        <item>oc-ES</item> <!-- Occitan (Spain) -->
+        <item>oc-FR</item> <!-- Occitan (France) -->
         <item>om-ET</item> <!-- Oromo (Ethiopia) -->
         <item>om-KE</item> <!-- Oromo (Kenya) -->
         <item>or-IN</item> <!-- Odia (India) -->
@@ -487,6 +515,7 @@
         <item>pa-Guru-IN</item> <!-- Punjabi (Gurmukhi, India) -->
         <item>pcm-NG</item> <!-- Nigerian Pidgin (Nigeria) -->
         <item>pl-PL</item> <!-- Polish (Poland) -->
+        <item>prg-PL</item> <!-- Prussian (Poland) -->
         <item>ps-AF</item> <!-- Pashto (Afghanistan) -->
         <item>ps-AF-u-nu-latn</item> <!-- Pashto (Afghanistan, Western Digits) -->
         <item>ps-PK</item> <!-- Pashto (Pakistan) -->
@@ -566,6 +595,9 @@
         <item>sw-KE</item> <!-- Swahili (Kenya) -->
         <item>sw-TZ</item> <!-- Swahili (Tanzania) -->
         <item>sw-UG</item> <!-- Swahili (Uganda) -->
+        <item>syr-IQ</item> <!-- Syriac (Iraq) -->
+        <item>syr-SY</item> <!-- Syriac (Syria) -->
+        <item>szl-PL</item> <!-- Silesian (Poland) -->
         <item>ta-IN</item> <!-- Tamil (India) -->
         <item>ta-LK</item> <!-- Tamil (Sri Lanka) -->
         <item>ta-MY</item> <!-- Tamil (Malaysia) -->
@@ -580,7 +612,7 @@
         <item>tk-TM</item> <!-- Turkmen (Turkmenistan) -->
         <item>to-TO</item> <!-- Tongan (Tonga) -->
         <item>tr-CY</item> <!-- Turkish (Cyprus) -->
-        <item>tr-TR</item> <!-- Turkish (Turkey) -->
+        <item>tr-TR</item> <!-- Turkish (Türkiye) -->
         <item>tt-RU</item> <!-- Tatar (Russia) -->
         <item>twq-NE</item> <!-- Tasawaq (Niger) -->
         <item>tzm-MA</item> <!-- Central Atlas Tamazight (Morocco) -->
@@ -594,11 +626,14 @@
         <item>uz-Arab-AF-u-nu-latn</item> <!-- Uzbek (Arabic, Afghanistan, Western Digits) -->
         <item>uz-Cyrl-UZ</item> <!-- Uzbek (Cyrillic, Uzbekistan) -->
         <item>uz-Latn-UZ</item> <!-- Uzbek (Latin, Uzbekistan) -->
+        <item>vec-IT</item> <!-- Venetian (Italy) -->
         <item>vi-VN</item> <!-- Vietnamese (Vietnam) -->
+        <item>vmw-MZ</item> <!-- Makhuwa (Mozambique) -->
         <item>vun-TZ</item> <!-- Vunjo (Tanzania) -->
         <item>wae-CH</item> <!-- Walser (Switzerland) -->
         <item>wo-SN</item> <!-- Wolof (Senegal) -->
         <item>xh-ZA</item> <!-- Xhosa (South Africa) -->
+        <item>xnr-IN</item> <!-- Kangri (India) -->
         <item>xog-UG</item> <!-- Soga (Uganda) -->
         <item>yav-CM</item> <!-- Yangben (Cameroon) -->
         <item>yo-BJ</item> <!-- Yoruba (Benin) -->
@@ -608,6 +643,7 @@
         <item>yrl-VE</item> <!-- Nheengatu (Venezuela) -->
         <item>yue-Hans-CN</item> <!-- Cantonese (Simplified, China) -->
         <item>yue-Hant-HK</item> <!-- Cantonese (Traditional, Hong Kong) -->
+        <item>za-CN</item> <!-- Zhuang (China) -->
         <item>zgh-MA</item> <!-- Standard Moroccan Tamazight (Morocco) -->
         <item>zh-Hans-CN</item> <!-- Chinese (Simplified, China) -->
         <item>zh-Hans-HK</item> <!-- Chinese (Simplified, Hong Kong) -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 50c3b1a..aabc8ca 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1527,6 +1527,24 @@
     </style>
 
     <!-- @hide -->
+    <style name="PointerIconVectorStyleStrokeWhite">
+        <item name="pointerIconVectorStroke">@color/white</item>
+        <item name="pointerIconVectorStrokeInverse">@color/black</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="PointerIconVectorStyleStrokeBlack">
+        <item name="pointerIconVectorStroke">@color/black</item>
+        <item name="pointerIconVectorStrokeInverse">@color/white</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="PointerIconVectorStyleStrokeNone">
+        <item name="pointerIconVectorStroke">@color/transparent</item>
+        <item name="pointerIconVectorStrokeInverse">@color/transparent</item>
+    </style>
+
+    <!-- @hide -->
     <style name="aerr_list_item" parent="Widget.Material.Light.Button.Borderless">
         <item name="minHeight">?attr/listPreferredItemHeightSmall</item>
         <item name="textAppearance">?attr/textAppearanceListItemSmall</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7d50d22..9661b46 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1704,6 +1704,10 @@
   <java-symbol type="style" name="PointerIconVectorStyleFillPink" />
   <java-symbol type="style" name="PointerIconVectorStyleFillBlue" />
   <java-symbol type="attr" name="pointerIconVectorFill" />
+  <java-symbol type="style" name="PointerIconVectorStyleStrokeWhite" />
+  <java-symbol type="style" name="PointerIconVectorStyleStrokeBlack" />
+  <java-symbol type="style" name="PointerIconVectorStyleStrokeNone" />
+  <java-symbol type="attr" name="pointerIconVectorStroke" />
   <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Title" />
   <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Info" />
 
@@ -2122,6 +2126,7 @@
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
   <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" />
   <java-symbol type="bool" name="config_defaultKeyboardVibrationEnabled" />
+  <java-symbol type="bool" name="config_keyboardVibrationSettingsSupported" />
   <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
   <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
   <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 612b387..9ea2943 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -50,6 +50,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -393,19 +394,31 @@
         if (splitAttributes == null) {
             return TaskFragmentAnimationParams.DEFAULT;
         }
+        final TaskFragmentAnimationParams.Builder builder =
+                new TaskFragmentAnimationParams.Builder();
         final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
-        TaskFragmentAnimationParams.Builder builder = new TaskFragmentAnimationParams.Builder();
-        if (animationBackgroundColor != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
-            builder.setAnimationBackgroundColor(animationBackgroundColor);
+        builder.setAnimationBackgroundColor(animationBackgroundColor);
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            final int openAnimationResId =
+                    splitAttributes.getAnimationParams().getOpenAnimationResId();
+            builder.setOpenAnimationResId(openAnimationResId);
+            final int closeAnimationResId =
+                    splitAttributes.getAnimationParams().getCloseAnimationResId();
+            builder.setCloseAnimationResId(closeAnimationResId);
+            final int changeAnimationResId =
+                    splitAttributes.getAnimationParams().getChangeAnimationResId();
+            builder.setChangeAnimationResId(changeAnimationResId);
         }
-        // TODO(b/293658614): Allow setting custom open/close/changeAnimationResId.
         return builder.build();
     }
 
     @ColorInt
     private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
         int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
-        final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+        AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            animationBackground = splitAttributes.getAnimationParams().getAnimationBackground();
+        }
         if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
             animationBackgroundColor = colorBackground.getColor();
         }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 4267749..c5aaddc 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -29,6 +29,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.window.extensions.embedding.AnimationBackground;
+import androidx.window.extensions.embedding.AnimationParams;
 import androidx.window.extensions.embedding.SplitAttributes;
 
 import org.junit.Before;
@@ -112,5 +113,13 @@
                 .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
         assertThat(splitAttributes.getAnimationBackground())
                 .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
+        assertThat(splitAttributes.getAnimationParams().getAnimationBackground())
+                .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
+        assertThat(splitAttributes.getAnimationParams().getOpenAnimationResId())
+                .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
+        assertThat(splitAttributes.getAnimationParams().getCloseAnimationResId())
+                .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
+        assertThat(splitAttributes.getAnimationParams().getChangeAnimationResId())
+                .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
     }
 }
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index c6dbd9b..1871203 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -22,6 +22,40 @@
     default_team: "trendy_team_multitasking_windowing",
 }
 
+android_app {
+    name: "WMShellRobolectricScreenshotTestApp",
+    platform_apis: true,
+    certificate: "platform",
+    static_libs: [
+        "WindowManager-Shell",
+        "platform-screenshot-diff-core",
+    ],
+    asset_dirs: ["goldens/robolectric"],
+    manifest: "AndroidManifestRobolectric.xml",
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "WMShellRobolectricScreenshotTests",
+    instrumentation_for: "WMShellRobolectricScreenshotTestApp",
+    upstream: true,
+    java_resource_dirs: [
+        "robolectric/config",
+    ],
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "truth",
+        "platform-parametric-runner-lib",
+    ],
+    auto_gen_config: true,
+}
+
 android_test {
     name: "WMShellMultivalentScreenshotTestsOnDevice",
     srcs: [
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
index a7a3f13..b4bdaea 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
@@ -16,7 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.wm.shell.multivalentscreenshot">
     <application android:debuggable="true" android:supportsRtl="true">
-        <uses-library android:name="android.test.runner" />
         <activity
             android:name="platform.test.screenshot.ScreenshotActivity"
             android:exported="true">
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
new file mode 100644
index 0000000..723c6b8
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
new file mode 100644
index 0000000..723c6b8
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
index 7a0527c..d50d976 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
@@ -1,2 +1,3 @@
 sdk=NEWEST_SDK
+graphicsMode=NATIVE
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
index 92084e4..67d46f4 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
@@ -16,21 +16,19 @@
 
 package com.android.wm.shell.shared;
 
-import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
-
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.SystemProperties;
-import android.provider.Settings;
-import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 
 /**
  * Constants for desktop mode feature
  */
+// TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package
 public class DesktopModeStatus {
 
     private static final String TAG = "DesktopModeStatus";
@@ -178,19 +176,7 @@
     public static boolean canEnterDesktopMode(@NonNull Context context) {
         if (!isDeviceEligibleForDesktopMode(context)) return false;
 
-        // If dev option has ever been manually toggled by the user, return its value
-        // TODO(b/348193756) : Move the logic for DW override based on toggle overides to a common
-        //  infrastructure and add caching for the computation
-        int defaultOverrideState = -1;
-        int toggleState = Settings.Global.getInt(context.getContentResolver(),
-                DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, defaultOverrideState);
-        if (toggleState != defaultOverrideState) {
-            Log.d(TAG, "Using Desktop mode dev option overridden state");
-            return toggleState != 0;
-        }
-
-        // Return Desktop windowing flag value
-        return isDesktopModeFlagEnabled();
+        return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
new file mode 100644
index 0000000..8f7fdd6
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.shared.desktopmode
+
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.window.flags.Flags
+import com.android.wm.shell.shared.DesktopModeStatus
+
+/*
+ * A shared class to check desktop mode flags state.
+ *
+ * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
+ * value and the developer option override state (if applicable).
+ **/
+enum class DesktopModeFlags(
+    // Function called to obtain aconfig flag value.
+    private val flagFunction: () -> Boolean,
+    // Whether the flag state should be affected by developer option.
+    private val shouldOverrideByDevOption: Boolean
+) {
+  // All desktop mode related flags will be added here
+  DESKTOP_WINDOWING_MODE(DesktopModeStatus::isDesktopModeFlagEnabled, true);
+
+  // Local cache for toggle override, which is initialized once on its first access. It needs to be
+  // refreshed only on reboots as overridden state takes effect on reboots.
+  private var cachedToggleOverride: ToggleOverride? = null
+
+  /**
+   * Determines state of flag based on the actual flag and desktop mode developer option overrides.
+   *
+   * Note, this method makes sure that a constant developer toggle overrides is read until reboot.
+   */
+  fun isEnabled(context: Context): Boolean =
+      if (!Flags.showDesktopWindowingDevOption() ||
+          !shouldOverrideByDevOption ||
+          context.contentResolver == null) {
+        flagFunction()
+      } else {
+        when (getToggleOverride(context)) {
+          ToggleOverride.OVERRIDE_UNSET -> flagFunction()
+          ToggleOverride.OVERRIDE_OFF -> false
+          ToggleOverride.OVERRIDE_ON -> true
+        }
+      }
+
+  private fun getToggleOverride(context: Context): ToggleOverride {
+    val override =
+        cachedToggleOverride
+            ?: run {
+              val override = getToggleOverrideFromSystem(context)
+              // Cache toggle override the first time we encounter context. Override does not change
+              // with context, as context is just used to fetch System Property and Settings.Global
+              cachedToggleOverride = override
+              Log.d(TAG, "Toggle override initialized to: $override")
+              override
+            }
+
+    return override
+  }
+
+  private fun getToggleOverrideFromSystem(context: Context): ToggleOverride {
+    // A non-persistent System Property is used to store override to ensure it remains
+    // constant till reboot.
+    val overrideFromSystemProperties: ToggleOverride? =
+        System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride()
+    return overrideFromSystemProperties
+        ?: run {
+          // Read Setting Global if System Property is not present (just after reboot)
+          // or not valid (user manually changed the value)
+          val overrideFromSettingsGlobal =
+              Settings.Global.getInt(
+                      context.contentResolver,
+                      Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+                      ToggleOverride.OVERRIDE_UNSET.setting)
+                  .convertToToggleOverrideWithFallback(ToggleOverride.OVERRIDE_UNSET)
+          // Initialize System Property
+          System.setProperty(
+              SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString())
+
+          overrideFromSettingsGlobal
+        }
+  }
+
+  // TODO(b/348193756): Share ToggleOverride enum with Settings 'DesktopModePreferenceController'
+  /**
+   * Override state of desktop mode developer option toggle.
+   *
+   * @property setting The integer value that is associated with the developer option toggle
+   *   override
+   */
+  enum class ToggleOverride(val setting: Int) {
+    /** No override is set. */
+    OVERRIDE_UNSET(-1),
+    /** Override to off. */
+    OVERRIDE_OFF(0),
+    /** Override to on. */
+    OVERRIDE_ON(1)
+  }
+
+  private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
+
+  private fun String?.convertToToggleOverride(): ToggleOverride? {
+    val intValue = this?.toIntOrNull() ?: return null
+    return settingToToggleOverrideMap[intValue]
+        ?: run {
+          Log.w(TAG, "Unknown toggleOverride int $intValue")
+          null
+        }
+  }
+
+  private fun Int.convertToToggleOverrideWithFallback(
+      fallbackOverride: ToggleOverride
+  ): ToggleOverride {
+    return settingToToggleOverrideMap[this]
+        ?: run {
+          Log.w(TAG, "Unknown toggleOverride int $this")
+          fallbackOverride
+        }
+  }
+
+  private companion object {
+    const val TAG = "DesktopModeFlags"
+
+    /**
+     * Key for non-persistent System Property which is used to store desktop windowing developer
+     * option overrides.
+     */
+    const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+  }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS
new file mode 100644
index 0000000..2fabd4a
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS
@@ -0,0 +1 @@
+file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d270d2b..5696a54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -266,6 +266,9 @@
             final Animation animation =
                     animationProvider.get(info, change, openingWholeScreenBounds);
             if (shouldUseJumpCutForAnimation(animation)) {
+                if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+                    return new ArrayList<>();
+                }
                 continue;
             }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
@@ -291,6 +294,9 @@
             final Animation animation =
                     animationProvider.get(info, change, closingWholeScreenBounds);
             if (shouldUseJumpCutForAnimation(animation)) {
+                if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+                    return new ArrayList<>();
+                }
                 continue;
             }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index f49b90d0..3046307 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -97,7 +97,7 @@
     Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
         if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            final Animation customAnimation = loadCustomAnimation(info, change);
+            final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
             if (customAnimation != null) {
                 return customAnimation;
             }
@@ -131,7 +131,7 @@
     Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
         if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            final Animation customAnimation = loadCustomAnimation(info, change);
+            final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
             if (customAnimation != null) {
                 return customAnimation;
             }
@@ -172,7 +172,7 @@
             // TODO(b/293658614): Support more complicated animations that may need more than a noop
             // animation as the start leash.
             final Animation noopAnimation = createNoopAnimation(change);
-            final Animation customAnimation = loadCustomAnimation(info, change);
+            final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
             if (customAnimation != null) {
                 return new Animation[]{noopAnimation, customAnimation};
             }
@@ -227,7 +227,7 @@
     Animation loadOpenAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
-        final Animation customAnimation = loadCustomAnimation(info, change);
+        final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
         final Animation animation;
         if (customAnimation != null) {
             animation = customAnimation;
@@ -254,7 +254,7 @@
     Animation loadCloseAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
-        final Animation customAnimation = loadCustomAnimation(info, change);
+        final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
         final Animation animation;
         if (customAnimation != null) {
             animation = customAnimation;
@@ -287,14 +287,14 @@
 
     @Nullable
     private Animation loadCustomAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change) {
+            @NonNull TransitionInfo.Change change, @WindowManager.TransitionType int mode) {
         final TransitionInfo.AnimationOptions options;
         if (Flags.moveAnimationOptionsToChange()) {
             options = change.getAnimationOptions();
         } else {
             options = info.getAnimationOptions();
         }
-        return loadCustomAnimationFromOptions(options, change.getMode());
+        return loadCustomAnimationFromOptions(options, mode);
     }
 
     @Nullable
@@ -319,8 +319,14 @@
             return null;
         }
 
-        final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
-                resId);
+        final Animation anim;
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            // TODO(b/293658614): Consider allowing custom animations from non-default packages.
+            // Enforce limiting to animations from the default "android" package for now.
+            anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
+        } else {
+            anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId);
+        }
         if (anim != null) {
             return anim;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 6781d08..d1b2347 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -19,6 +19,16 @@
 package com.android.wm.shell.compatui
 
 import android.app.TaskInfo
-fun isSingleTopActivityTranslucent(task: TaskInfo) =
-    task.isTopActivityTransparent && task.numActivities == 1
+import android.content.Context
+import com.android.internal.R
 
+// TODO(b/347289970): Consider replacing with API
+fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
+    isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
+            && !task.isTopActivityStyleFloating)
+
+private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
+    val sysUiPackageName: String =
+        context.resources.getString(R.string.config_systemUi)
+    return task.baseActivity?.packageName == sysUiPackageName
+}
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 da1d6da..e792f7a 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
@@ -24,7 +24,6 @@
 import android.os.UserManager;
 import android.view.Choreographer;
 import android.view.IWindowManager;
-import android.view.SurfaceControl;
 import android.view.WindowManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
@@ -404,8 +403,7 @@
             Optional<RecentTasksController> recentTasksController,
             HomeTransitionObserver homeTransitionObserver) {
         return new RecentsTransitionHandler(shellInit, transitions,
-                recentTasksController.orElse(null), homeTransitionObserver,
-                SurfaceControl.Transaction::new);
+                recentTasksController.orElse(null), homeTransitionObserver);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 81891ce..df79b15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -250,6 +250,10 @@
         return ArraySet(displayData[displayId]?.activeTasks)
     }
 
+    /** Returns the minimized tasks for the given [displayId]. */
+    fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
+        ArraySet(displayData[displayId]?.minimizedTasks)
+
     /**
      * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
      * are visible.
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 7cc01d5..e247912 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
@@ -66,7 +66,7 @@
 import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
-import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
+import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.draganddrop.DragAndDropController
@@ -158,8 +158,6 @@
                 visualIndicator = null
             }
         }
-    private val sysUIPackageName = context.resources.getString(
-        com.android.internal.R.string.config_systemUi)
 
     private val transitionAreaHeight
         get() =
@@ -219,11 +217,6 @@
         return visualIndicator
     }
 
-    // TODO(b/347289970): Consider replacing with API
-    private fun isSystemUIApplication(taskInfo: RunningTaskInfo): Boolean {
-        return taskInfo.baseActivity?.packageName == sysUIPackageName
-    }
-
     fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
         toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
         enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -351,19 +344,12 @@
         wct: WindowContainerTransaction = WindowContainerTransaction(),
         transitionSource: DesktopModeTransitionSource,
     ) {
-        if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
+        if (Flags.enableDesktopWindowingModalsPolicy()
+            && isTopActivityExemptFromDesktopWindowing(context, task)) {
             KtProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
                 "DesktopTasksController: Cannot enter desktop, " +
-                    "translucent top activity found. This is likely a modal dialog."
-            )
-            return
-        }
-        if (isSystemUIApplication(task)) {
-            KtProtoLog.w(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: Cannot enter desktop, " +
-                        "systemUI top activity found."
+                        "ineligible top activity found."
             )
             return
         }
@@ -417,7 +403,7 @@
         )
         val wct = WindowContainerTransaction()
         exitSplitIfApplicable(wct, taskInfo)
-        moveHomeTask(wct, true /* toTop */)
+        moveHomeTask(wct, toTop = true)
         val taskToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
         addMoveToDesktopChanges(wct, taskInfo)
@@ -795,7 +781,7 @@
             addWallpaperActivity(wct)
         } else {
             // Move home to front
-            moveHomeTask(wct, true /* toTop */)
+            moveHomeTask(wct, toTop = true)
         }
 
         val nonMinimizedTasksOrderedFrontToBack =
@@ -942,10 +928,8 @@
                 when {
                     // Check if the closing task needs to be handled
                     TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
-                    // Check if the task has a top transparent activity
-                    shouldLaunchAsModal(task) -> handleIncompatibleTaskLaunch(task)
-                    // Check if the task has a top systemUI activity
-                    isSystemUIApplication(task) -> handleIncompatibleTaskLaunch(task)
+                    // Check if the top task shouldn't be allowed to enter desktop mode
+                    isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
                     // Check if fullscreen task should be updated
                     task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
                     // Check if freeform task should be updated
@@ -979,9 +963,9 @@
             .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
     }
 
-    // TODO(b/347289970): Consider replacing with API
-    private fun shouldLaunchAsModal(task: TaskInfo) =
-        Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
+    private fun isIncompatibleTask(task: TaskInfo) =
+        Flags.enableDesktopWindowingModalsPolicy()
+                && isTopActivityExemptFromDesktopWindowing(context, task)
 
     private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
         return Flags.enableDesktopWindowingWallpaperActivity() &&
@@ -1042,7 +1026,7 @@
                 addMoveToDesktopChanges(wct, task)
                 // In some launches home task is moved behind new task being launched. Make sure
                 // that's not the case for launches in desktop.
-                moveHomeTask(wct, false /* toTop */)
+                moveHomeTask(wct, toTop = false)
                 // Desktop Mode is already showing and we're launching a new Task - we might need to
                 // minimize another Task.
                 val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 0f88384..c85f76d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -42,9 +42,12 @@
         private val shellTaskOrganizer: ShellTaskOrganizer,
 ) {
     private val minimizeTransitionObserver = MinimizeTransitionObserver()
+    @VisibleForTesting
+    val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
 
     init {
         transitions.registerObserver(minimizeTransitionObserver)
+        taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
     }
 
     private data class TaskDetails (val displayId: Int, val taskId: Int)
@@ -113,6 +116,35 @@
         }
     }
 
+    @VisibleForTesting
+    inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+        override fun onActiveTasksChanged(displayId: Int) {
+            val wct = WindowContainerTransaction()
+            removeLeftoverMinimizedTasks(displayId, wct)
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+
+        fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
+            if (taskRepository
+                .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) {
+                return
+            }
+            val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
+            if (remainingMinimizedTasks.isEmpty()) {
+                return
+            }
+            KtProtoLog.v(
+                ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksLimiter: removing leftover minimized tasks: $remainingMinimizedTasks")
+            remainingMinimizedTasks.forEach { taskIdToRemove ->
+                val taskToRemove = shellTaskOrganizer.getRunningTaskInfo(taskIdToRemove)
+                if (taskToRemove != null) {
+                    wct.removeTask(taskToRemove.token)
+                }
+            }
+        }
+    }
+
     /**
      * Mark a task as minimized, this should only be done after the corresponding transition has
      * finished so we don't minimize the task if the transition fails.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index e46625d..234b4d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -74,7 +74,6 @@
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
@@ -85,7 +84,6 @@
 
     private final Transitions mTransitions;
     private final ShellExecutor mExecutor;
-    private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     @Nullable
     private final RecentTasksController mRecentTasksController;
     private IApplicationThread mAnimApp = null;
@@ -103,13 +101,11 @@
 
     public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
             @Nullable RecentTasksController recentTasksController,
-            HomeTransitionObserver homeTransitionObserver,
-            Supplier<SurfaceControl.Transaction> transactionSupplier) {
+            HomeTransitionObserver homeTransitionObserver) {
         mTransitions = transitions;
         mExecutor = transitions.getMainExecutor();
         mRecentTasksController = recentTasksController;
         mHomeTransitionObserver = homeTransitionObserver;
-        mTransactionSupplier = transactionSupplier;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
         if (recentTasksController == null) return;
         shellInit.addInitCallback(() -> {
@@ -1060,7 +1056,7 @@
             final Transitions.TransitionFinishCallback finishCB = mFinishCB;
             mFinishCB = null;
 
-            SurfaceControl.Transaction t = mFinishTransaction;
+            final SurfaceControl.Transaction t = mFinishTransaction;
             final WindowContainerTransaction wct = new WindowContainerTransaction();
 
             if (mKeyguardLocked && mRecentsTask != null) {
@@ -1110,16 +1106,6 @@
                     }
                 }
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  normal finish");
-                if (toHome && !mOpeningTasks.isEmpty()) {
-                    // Attempting to start a task after swipe to home, don't show it,
-                    // move recents to top
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  attempting to start a task after swipe to home");
-                    t = mTransactionSupplier.get();
-                    wct.reorder(mRecentsTask, true /*onTop*/);
-                    mClosingTasks.addAll(mOpeningTasks);
-                    mOpeningTasks.clear();
-                }
                 // The general case: committing to recents, going home, or switching tasks.
                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
                     t.show(mOpeningTasks.get(i).mTaskSurface);
@@ -1188,10 +1174,6 @@
                     mPipTransaction = null;
                 }
             }
-            if (t != mFinishTransaction) {
-                // apply after merges because these changes are accounting for finishWCT changes.
-                mTransitions.setAfterMergeFinishTransaction(mTransition, t);
-            }
             cleanUp();
             finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
             if (runnerFinishCb != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index ec6802d..bd25846 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -28,6 +28,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
+import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -241,13 +242,6 @@
         /** Ordered list of transitions which have been merged into this one. */
         private ArrayList<ActiveTransition> mMerged;
 
-        /**
-         * @deprecated DO NOT USE THIS unless absolutely necessary. It will be removed once
-         * everything migrates off finishWCT.
-         */
-        @java.lang.Deprecated
-        SurfaceControl.Transaction mAfterMergeFinishT;
-
         ActiveTransition(IBinder token) {
             mToken = token;
         }
@@ -1033,20 +1027,6 @@
         return null;
     }
 
-    /** @deprecated */
-    @java.lang.Deprecated
-    public void setAfterMergeFinishTransaction(IBinder transition,
-            SurfaceControl.Transaction afterMergeFinishT) {
-        final ActiveTransition at = mKnownTransitions.get(transition);
-        if (at == null) return;
-        if (at.mAfterMergeFinishT != null) {
-            Log.e(TAG, "Setting after-merge-t >1 time on transition: " + at.mInfo.getDebugId());
-            at.mAfterMergeFinishT.merge(afterMergeFinishT);
-            return;
-        }
-        at.mAfterMergeFinishT = afterMergeFinishT;
-    }
-
     /** Aborts a transition. This will still queue it up to maintain order. */
     private void onAbort(ActiveTransition transition) {
         final Track track = mTracks.get(transition.getTrack());
@@ -1107,7 +1087,6 @@
         }
         // Merge all associated transactions together
         SurfaceControl.Transaction fullFinish = active.mFinishT;
-        SurfaceControl.Transaction afterMergeFinish = active.mAfterMergeFinishT;
         if (active.mMerged != null) {
             for (int iM = 0; iM < active.mMerged.size(); ++iM) {
                 final ActiveTransition toMerge = active.mMerged.get(iM);
@@ -1127,21 +1106,6 @@
                         fullFinish.merge(toMerge.mFinishT);
                     }
                 }
-                if (toMerge.mAfterMergeFinishT != null) {
-                    if (afterMergeFinish == null) {
-                        afterMergeFinish = toMerge.mAfterMergeFinishT;
-                    } else {
-                        afterMergeFinish.merge(toMerge.mAfterMergeFinishT);
-                    }
-                    toMerge.mAfterMergeFinishT = null;
-                }
-            }
-        }
-        if (afterMergeFinish != null) {
-            if (fullFinish == null) {
-                fullFinish = afterMergeFinish;
-            } else {
-                fullFinish.merge(afterMergeFinish);
             }
         }
         if (fullFinish != null) {
@@ -1231,7 +1195,7 @@
     public IBinder startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
-                + "type=%d wct=%s handler=%s", type, wct, handler);
+                + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler);
         final ActiveTransition active =
                 new ActiveTransition(mOrganizer.startNewTransition(type, wct));
         active.mHandler = handler;
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 e3aa31f..440fc4d 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
@@ -32,7 +32,7 @@
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
+import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -105,7 +105,6 @@
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Supplier;
 
@@ -1034,12 +1033,8 @@
                 && taskInfo.isFocused) {
             return false;
         }
-        // TODO(b/347289970): Consider replacing with API
         if (Flags.enableDesktopWindowingModalsPolicy()
-                && isSingleTopActivityTranslucent(taskInfo)) {
-            return false;
-        }
-        if (isSystemUIApplication(taskInfo)) {
+                && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
             return false;
         }
         return DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -1118,14 +1113,6 @@
                 && mSplitScreenController.isTaskInSplitScreen(taskId);
     }
 
-    // TODO(b/347289970): Consider replacing with API
-    private boolean isSystemUIApplication(RunningTaskInfo taskInfo) {
-        if (taskInfo.baseActivity != null) {
-            return (Objects.equals(taskInfo.baseActivity.getPackageName(), mSysUIPackageName));
-        }
-        return false;
-    }
-
     private void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + "DesktopModeWindowDecorViewModel");
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 a0a61fe2..d0e8215 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
@@ -117,12 +117,10 @@
 
     /**
      * Checks that all parts of the screen are covered at the start and end of the transition
-     *
-     * TODO b/197726599 Prevents all states from being checked
      */
     @Presubmit
     @Test
-    fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
+    fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered()
 
     /** Checks [pipApp] window remains visible and on top throughout the transition */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 4cd2a36..ecaf970 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -16,8 +16,10 @@
 
 package com.android.wm.shell.compatui
 
+import android.content.ComponentName
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.internal.R
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import org.junit.Assert.assertFalse
@@ -34,26 +36,55 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class AppCompatUtilsTest : ShellTestCase() {
-
     @Test
-    fun testIsSingleTopActivityTranslucent() {
-        assertTrue(isSingleTopActivityTranslucent(
+    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+        assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
                     .apply {
                         isTopActivityTransparent = true
                         numActivities = 1
                     }))
-        assertFalse(isSingleTopActivityTranslucent(
+        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
                     .apply {
                         isTopActivityTransparent = true
                         numActivities = 0
                     }))
-        assertFalse(isSingleTopActivityTranslucent(
+    }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
+        assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+            createFreeformTask(/* displayId */ 0)
+                    .apply {
+                        isTopActivityTransparent = true
+                        numActivities = 1
+                    }))
+        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
                     .apply {
                         isTopActivityTransparent = false
                         numActivities = 1
                     }))
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+            createFreeformTask(/* displayId */ 0)
+                    .apply {
+                        isTopActivityStyleFloating = true
+                    }))
+    }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask() {
+        val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+        assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+            createFreeformTask(/* displayId */ 0)
+                    .apply {
+                        baseActivity = baseComponent
+                    }))
+    }
+}
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 a3b0dc5..bd38d36 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
@@ -264,7 +264,7 @@
 
   @Test
   fun instantiate_flagOff_doNotAddInitCallback() {
-    whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(false)
+    whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
     clearInvocations(shellInit)
 
     createController()
@@ -700,19 +700,37 @@
   }
 
   @Test
-  fun moveToDesktop_topActivityTranslucent_doesNothing() {
-    setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun moveToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
     val task =
-        setUpFullscreenTask().apply {
-          isTopActivityTransparent = true
-          numActivities = 1
-        }
+      setUpFullscreenTask().apply {
+        isTopActivityTransparent = true
+        isTopActivityStyleFloating = true
+        numActivities = 1
+      }
+
+    controller.moveToDesktop(task, transitionSource = UNKNOWN)
+
+    val wct = getLatestEnterDesktopWct()
+    assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun moveToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+    val task =
+      setUpFullscreenTask().apply {
+        isTopActivityTransparent = true
+        isTopActivityStyleFloating = false
+        numActivities = 1
+      }
 
     controller.moveToDesktop(task, transitionSource = UNKNOWN)
     verifyEnterDesktopWCTNotExecuted()
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
   fun moveToDesktop_systemUIActivity_doesNothing() {
     val task = setUpFullscreenTask()
 
@@ -1093,10 +1111,12 @@
     val fullscreenTask = createFullscreenTask()
 
     val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-    assertThat(wct?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+
+    assertNotNull(wct, "should handle request")
+    assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
         .isEqualTo(WINDOWING_MODE_FREEFORM)
 
-    assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
+    assertThat(wct.hierarchyOps).hasSize(2)
     wct.assertReorderAt(1, homeTask, toTop = false)
   }
 
@@ -1372,20 +1392,40 @@
   }
 
   @Test
-  fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() {
-    setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+
     val task =
-        setUpFreeformTask().apply {
-          isTopActivityTransparent = true
-          numActivities = 1
-        }
+      setUpFullscreenTask().apply {
+        isTopActivityTransparent = true
+        isTopActivityStyleFloating = true
+        numActivities = 1
+      }
 
     val result = controller.handleRequest(Binder(), createTransition(task))
     assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
-        .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+    val task =
+      setUpFreeformTask().apply {
+        isTopActivityTransparent = true
+        isTopActivityStyleFloating = false
+        numActivities = 1
+      }
+
+    val result = controller.handleRequest(Binder(), createTransition(task))
+    assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
   fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
     val task = setUpFreeformTask()
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 77f917c..4bfa96a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -24,6 +24,7 @@
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_BACK
 import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -205,6 +206,46 @@
     }
 
     @Test
+    fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
+        desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
+        desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+        desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+
+        val wct = WindowContainerTransaction()
+        desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+            DEFAULT_DISPLAY, wct)
+
+        assertThat(wct.isEmpty).isTrue()
+    }
+
+    @Test
+    fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
+        val wct = WindowContainerTransaction()
+        desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+            DEFAULT_DISPLAY, wct)
+
+        assertThat(wct.isEmpty).isTrue()
+    }
+
+    @Test
+    fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
+        val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+        desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+        val wct = WindowContainerTransaction()
+        desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+            DEFAULT_DISPLAY, wct)
+
+        assertThat(wct.hierarchyOps).hasSize(2)
+        assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+        assertThat(wct.hierarchyOps[0].container).isEqualTo(task1.token.asBinder())
+        assertThat(wct.hierarchyOps[1].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+        assertThat(wct.hierarchyOps[1].container).isEqualTo(task2.token.asBinder())
+    }
+
+    @Test
     fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
         val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
         (1..<taskLimit).forEach { _ -> setUpFreeformTask() }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
new file mode 100644
index 0000000..17983b2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -0,0 +1,334 @@
+/*
+ * 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.shared.desktopmode
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+/**
+ * Test class for [DesktopModeFlags]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeFlagsTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeFlagsTest : ShellTestCase() {
+
+  @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+  @Before
+  fun setUp() {
+    resetCache()
+  }
+
+  // TODO(b/348193756): Add tests
+  // isEnabled_flagNotOverridable_overrideOff_featureFlagOn_returnsTrue and
+  // isEnabled_flagNotOverridable_overrideOn_featureFlagOff_returnsFalse after adding non
+  // overridable flags to DesktopModeFlags.
+
+  @Test
+  @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+    setOverride(OVERRIDE_OFF.setting)
+
+    // In absence of dev options, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+  }
+
+  @Test
+  @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
+    setOverride(OVERRIDE_ON.setting)
+
+    // In absence of dev options, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_overrideUnset_featureFlagOn_returnsTrue() {
+    setOverride(OVERRIDE_UNSET.setting)
+
+    // For overridableFlag, for unset overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_overrideUnset_featureFlagOff_returnsFalse() {
+    setOverride(OVERRIDE_UNSET.setting)
+
+    // For overridableFlag, for unset overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_noOverride_featureFlagOn_returnsTrue() {
+    setOverride(null)
+
+    // For overridableFlag, in absence of overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_noOverride_featureFlagOff_returnsFalse() {
+    setOverride(null)
+
+    // For overridableFlag, in absence of overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() {
+    setOverride(-2)
+
+    // For overridableFlag, for recognizable overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() {
+    setOverride(-2)
+
+    // For overridableFlag, for recognizable overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_overrideOff_featureFlagOn_returnsFalse() {
+    setOverride(OVERRIDE_OFF.setting)
+
+    // For overridableFlag, follow override if they exist
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_overrideOn_featureFlagOff_returnsTrue() {
+    setOverride(OVERRIDE_ON.setting)
+
+    // For overridableFlag, follow override if they exist
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
+    setOverride(OVERRIDE_OFF.setting)
+
+    // For overridableFlag, follow override if they exist
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+
+    setOverride(OVERRIDE_ON.setting)
+
+    // Keep overrides constant through the process
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
+    setOverride(OVERRIDE_ON.setting)
+
+    // For overridableFlag, follow override if they exist
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+
+    setOverride(OVERRIDE_OFF.setting)
+
+    // Keep overrides constant through the process
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_noOverride_featureFlagOnThenOff_returnsTrueAndFalse() {
+    setOverride(null)
+    // For overridableFlag, in absence of overrides, follow flag
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+
+    val mockitoSession: StaticMockitoSession =
+        ExtendedMockito.mockitoSession()
+            .strictness(Strictness.LENIENT)
+            .spyStatic(DesktopModeStatus::class.java)
+            .startMocking()
+    try {
+      // No caching of flags
+      whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(false)
+      assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+    } finally {
+      mockitoSession.finishMocking()
+    }
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() {
+    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+    setOverride(OVERRIDE_ON.setting)
+
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+    // Store System Property if not present
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_ON.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() {
+    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+    setOverride(OVERRIDE_UNSET.setting)
+
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+    // Store System Property if not present
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_UNSET.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() {
+    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+    setOverride(OVERRIDE_UNSET.setting)
+
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+    // Store System Property if not present
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_UNSET.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  @Suppress("ktlint:standard:max-line-length")
+  fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
+    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc")
+    setOverride(OVERRIDE_OFF.setting)
+
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+    // Store System Property if currently invalid
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_OFF.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  @Suppress("ktlint:standard:max-line-length")
+  fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
+    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2")
+    setOverride(OVERRIDE_OFF.setting)
+
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+    // Store System Property if currently invalid
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_OFF.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() {
+    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString())
+    setOverride(OVERRIDE_ON.setting)
+
+    // Have a consistent override until reboot
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_OFF.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() {
+    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString())
+    setOverride(OVERRIDE_OFF.setting)
+
+    // Have a consistent override until reboot
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_ON.setting.toString())
+  }
+
+  @Test
+  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+  @Suppress("ktlint:standard:max-line-length")
+  fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() {
+    System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString())
+    setOverride(OVERRIDE_OFF.setting)
+
+    // Have a consistent override until reboot
+    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+    assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+        .isEqualTo(OVERRIDE_UNSET.setting.toString())
+  }
+
+  private fun setOverride(setting: Int?) {
+    val contentResolver = mContext.contentResolver
+    val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
+    if (setting == null) {
+      Settings.Global.putString(contentResolver, key, null)
+    } else {
+      Settings.Global.putInt(contentResolver, key, setting)
+    }
+  }
+
+  private fun resetCache() {
+    val cachedToggleOverride =
+        DESKTOP_WINDOWING_MODE::class.java.getDeclaredField("cachedToggleOverride")
+    cachedToggleOverride.isAccessible = true
+    cachedToggleOverride.set(DESKTOP_WINDOWING_MODE, null)
+
+    // Clear override cache stored in System property
+    System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+  }
+
+  private companion object {
+    const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+  }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS
new file mode 100644
index 0000000..2fabd4a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS
@@ -0,0 +1 @@
+file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 8331d59..409b877 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1191,8 +1191,7 @@
                         mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
         final RecentsTransitionHandler recentsHandler =
                 new RecentsTransitionHandler(shellInit, transitions,
-                        mock(RecentTasksController.class), mock(HomeTransitionObserver.class),
-                        () -> mock(SurfaceControl.Transaction.class));
+                        mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
         shellInit.init();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 0ec6713..0bf5a67 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -348,10 +348,35 @@
     }
 
     @Test
-    fun testDecorationIsNotCreatedForTopTranslucentActivities() {
-        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+        val mockitoSession: StaticMockitoSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(DesktopModeStatus::class.java)
+                .startMocking()
+        try {
+            val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+                isTopActivityTransparent = true
+                isTopActivityStyleFloating = true
+                numActivities = 1
+            }
+            doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+            setUpMockDecorationsForTasks(task)
+
+            onTaskOpening(task)
+            verify(mockDesktopModeWindowDecorFactory)
+                    .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
             isTopActivityTransparent = true
+            isTopActivityStyleFloating = false
             numActivities = 1
         }
         onTaskOpening(task)
@@ -361,6 +386,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun testDecorationIsNotCreatedForSystemUIActivities() {
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
 
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index 1cb8df3..ad445c0 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -132,7 +132,7 @@
 
   auto rhs_iter = rhs.entry_->spans.begin();
   for (const Span& span : entry_->spans) {
-    const Span& rhs_span = *rhs_iter;
+    const Span& rhs_span = *rhs_iter++;
     if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char ||
         span.name != rhs_span.name) {
       return false;
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index c36b82d..71ecb4c 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -19,7 +19,6 @@
 
     static_libs: [
         "androidx.preference_preference",
-        "SettingsLibUtils",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index 3e65d94..5dc11cf 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -31,12 +32,11 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.preference.actionbuttons.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
-import com.android.settingslib.widget.preference.actionbuttons.R;
-
 /**
  * This preference provides a four buttons layout with Settings style.
  * It looks like below
@@ -56,7 +56,7 @@
 public class ActionButtonsPreference extends Preference {
 
     private static final String TAG = "ActionButtonPreference";
-    private static final boolean mIsAtLeastS = BuildCompatUtils.isAtLeastS();
+    private static final boolean mIsAtLeastS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
     private static final int SINGLE_BUTTON_STYLE = 1;
     private static final int TWO_BUTTONS_STYLE = 2;
     private static final int THREE_BUTTONS_STYLE = 3;
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 838a9e5..5561002 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -20,7 +20,6 @@
         "androidx.annotation_annotation",
         "androidx.core_core",
         "androidx.window_window",
-        "SettingsLibUtils",
     ],
     sdk_version: "system_current",
     min_sdk_version: "21",
diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
index f89be9f..67aa8f4 100644
--- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
+++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
@@ -20,13 +20,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.util.Log;
 
-import androidx.core.os.BuildCompat;
 import androidx.window.embedding.ActivityEmbeddingController;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 /**
  * An util class collecting all common methods for the embedding activity features.
  */
@@ -70,7 +68,7 @@
      * enabled (unsupported devices).
      */
     private static ComponentName getEmbeddingActivityComponent(Context context) {
-        if (!BuildCompatUtils.isAtLeastSV2()) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S_V2) {
             return null;
         }
         final Intent intent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
@@ -95,7 +93,7 @@
      *                          Settings app
      */
     public static boolean shouldHideNavigateUpButton(Activity activity, boolean isSecondLayerPage) {
-        if (!BuildCompat.isAtLeastT()) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
             return false;
         }
 
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 07290de..3f671b9 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -20,7 +20,6 @@
     static_libs: [
         "androidx.preference_preference",
         "SettingsLibSettingsTheme",
-        "SettingsLibUtils",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 33775a6..6cd777e 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -38,7 +38,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.utils.BuildCompatUtils;
 import com.android.settingslib.widget.preference.banner.R;
 /**
  * Banner message is a banner displaying important information (permission request, page error etc),
@@ -84,7 +83,7 @@
     }
 
     private static final String TAG = "BannerPreference";
-    private static final boolean IS_AT_LEAST_S = BuildCompatUtils.isAtLeastS();
+    private static final boolean IS_AT_LEAST_S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
 
     private final BannerMessagePreference.ButtonInfo mPositiveButtonInfo =
             new BannerMessagePreference.ButtonInfo();
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 87ec0b8..4834039 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -22,7 +22,6 @@
         "androidx.core_core",
         "com.google.android.material_material",
         "SettingsLibSettingsTransition",
-        "SettingsLibUtils",
         "SettingsLibSettingsTheme",
     ],
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
index 8ebbac3..b252e5f 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
@@ -16,11 +16,11 @@
 
 package com.android.settingslib.collapsingtoolbar;
 
+import android.os.Build;
+
 import androidx.fragment.app.FragmentActivity;
 import androidx.preference.PreferenceFragmentCompat;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 import com.google.android.material.appbar.AppBarLayout;
 
 /**
@@ -58,7 +58,7 @@
         if (activity != null) {
             activity.setTitle(getTitle());
 
-            if (BuildCompatUtils.isAtLeastS()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                 AppBarLayout appBarLayout = (AppBarLayout) activity.findViewById(R.id.app_bar);
 
                 if (appBarLayout != null) {
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
index 04c44e6..8b27626 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.collapsingtoolbar;
 
 import android.app.ActionBar;
+import android.os.Build;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -26,8 +27,6 @@
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.appbar.CollapsingToolbarLayout;
 import com.google.android.material.color.DynamicColors;
@@ -66,12 +65,12 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (BuildCompatUtils.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             DynamicColors.applyToActivityIfAvailable(this);
         }
         setTheme(com.android.settingslib.widget.theme.R.style.Theme_SubSettingsBase);
 
-        if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
+        if (mCustomizeLayoutResId > 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
             super.setContentView(mCustomizeLayoutResId);
             return;
         }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 143101f..86ce2ab 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.ActionBar;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -27,8 +28,6 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.appbar.CollapsingToolbarLayout;
 
@@ -60,7 +59,8 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         // for backward compatibility on R devices or wearable devices due to small device size.
-        if (mCustomizeLayoutResId > 0 && (!BuildCompatUtils.isAtLeastS() || isWatch())) {
+        if (mCustomizeLayoutResId > 0 && (Build.VERSION.SDK_INT < Build.VERSION_CODES.S
+                || isWatch())) {
             super.setContentView(mCustomizeLayoutResId);
             return;
         }
diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
index c52386b..7be4482 100644
--- a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
+++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
@@ -30,7 +30,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.core.os.BuildCompat;
 import androidx.fragment.app.Fragment;
 import androidx.viewpager2.widget.ViewPager2;
 
@@ -226,7 +225,8 @@
     // to be here only for this API level - when then private profile was introduced.
     @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     private boolean shouldShowPrivateProfileIfItsOne(UserHandle userHandle) {
-        if (!BuildCompat.isAtLeastV() || !android.os.Flags.allowPrivateProfile()) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM
+                || !android.os.Flags.allowPrivateProfile()) {
             return false;
         }
         try {
diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
deleted file mode 100644
index bf3651b..0000000
--- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
+++ /dev/null
@@ -1,60 +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.settingslib.utils;
-
-import android.os.Build;
-
-import androidx.annotation.ChecksSdkIntAtLeast;
-
-/**
- * An util class to check whether the current OS version is higher or equal to sdk version of
- * device.
- */
-public final class BuildCompatUtils {
-
-    /**
-     * Implementation of BuildCompat.isAtLeastS() suitable for use in Settings
-     *
-     * @return Whether the current OS version is higher or equal to S.
-     */
-    @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
-    public static boolean isAtLeastS() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
-    }
-
-    /**
-     * Implementation of BuildCompat.isAtLeastS() suitable for use in Settings
-     *
-     * @return Whether the current OS version is higher or equal to Sv2.
-     */
-    @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S_V2)
-    public static boolean isAtLeastSV2() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2;
-    }
-
-    /**
-     * Implementation of BuildCompat.isAtLeastT() suitable for use in Settings
-     *
-     * @return Whether the current OS version is higher or equal to T.
-     */
-    @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
-    public static boolean isAtLeastT() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
-    }
-
-    private BuildCompatUtils() {}
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 734b92c..6ca9279 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -38,8 +38,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 /**
  * Helper class for managing settings preferences that can be disabled
  * by device admins via user restrictions.
@@ -120,9 +118,10 @@
         if (mDisabledSummary) {
             final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
             if (summaryView != null) {
-                final CharSequence disabledText = BuildCompatUtils.isAtLeastT()
-                        ? getDisabledByAdminUpdatableString()
-                        : mContext.getString(R.string.disabled_by_admin_summary_text);
+                final CharSequence disabledText =
+                        (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+                                ? getDisabledByAdminUpdatableString()
+                                : mContext.getString(R.string.disabled_by_admin_summary_text);
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
                 } else if (mDisabledByEcm) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 45754eb..fffbb54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -25,6 +25,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.AttributeSet;
@@ -40,8 +41,6 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreferenceCompat;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 /**
  * Version of SwitchPreferenceCompat that can be disabled by a device admin
  * using a user restriction.
@@ -164,7 +163,7 @@
 
     private static String getUpdatableEnterpriseString(
             Context context, String updatableStringId, int resId) {
-        if (!BuildCompatUtils.isAtLeastT()) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
             return context.getString(resId);
         }
         return context.getSystemService(DevicePolicyManager.class).getResources().getString(
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index c2506d3..b02b0c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -66,7 +66,6 @@
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.settingslib.fuelgauge.BatteryUtils;
-import com.android.settingslib.utils.BuildCompatUtils;
 
 import java.util.List;
 
@@ -147,7 +146,7 @@
         String name = info != null ? info.name : null;
         if (info.isManagedProfile()) {
             // We use predefined values for managed profiles
-            return BuildCompatUtils.isAtLeastT()
+            return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
                     ? getUpdatableManagedUserTitle(context)
                     : context.getString(R.string.managed_user_title);
         } else if (info.isGuest()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 721e7b9..53441c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -24,6 +24,7 @@
 import android.net.Uri;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -808,7 +809,8 @@
      * <p>If CachedBluetoothDevice#getGroupId is invalid, fetch group id from
      * LeAudioProfile#getGroupId.
      */
-    public static int getGroupId(@NonNull CachedBluetoothDevice cachedDevice) {
+    public static int getGroupId(@Nullable CachedBluetoothDevice cachedDevice) {
+        if (cachedDevice == null) return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
         int groupId = cachedDevice.getGroupId();
         String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
         if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
@@ -824,4 +826,44 @@
         Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
         return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
     }
+
+    /** Get primary device Uri in broadcast. */
+    @NonNull
+    public static String getPrimaryGroupIdUriForBroadcast() {
+        return "bluetooth_le_broadcast_fallback_active_group_id";
+    }
+
+    /** Get primary device group id in broadcast. */
+    @WorkerThread
+    public static int getPrimaryGroupIdForBroadcast(@NonNull Context context) {
+        return Settings.Secure.getInt(
+                context.getContentResolver(),
+                getPrimaryGroupIdUriForBroadcast(),
+                BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+    }
+
+    /** Get secondary {@link CachedBluetoothDevice} in broadcast. */
+    @Nullable
+    @WorkerThread
+    public static CachedBluetoothDevice getSecondaryDeviceForBroadcast(
+            @NonNull Context context, @Nullable LocalBluetoothManager localBtManager) {
+        if (localBtManager == null) return null;
+        int primaryGroupId = getPrimaryGroupIdForBroadcast(context);
+        if (primaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return null;
+        LocalBluetoothLeBroadcastAssistant assistant =
+                localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        CachedBluetoothDeviceManager deviceManager = localBtManager.getCachedDeviceManager();
+        List<BluetoothDevice> devices = assistant.getAllConnectedDevices();
+        for (BluetoothDevice device : devices) {
+            CachedBluetoothDevice cachedDevice = deviceManager.findDevice(device);
+            if (hasConnectedBroadcastSource(cachedDevice, localBtManager)) {
+                int groupId = getGroupId(cachedDevice);
+                if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+                        && groupId != primaryGroupId) {
+                    return cachedDevice;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index ea65ade..84afb9f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -25,7 +25,6 @@
 import androidx.annotation.EmptySuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
-import androidx.core.os.BuildCompat;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceGroup;
@@ -141,7 +140,7 @@
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     protected void replaceEnterpriseStringTitle(PreferenceScreen screen,
             String preferenceKey, String overrideKey, int resource) {
-        if (!BuildCompat.isAtLeastT() || mDevicePolicyManager == null) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || mDevicePolicyManager == null) {
             return;
         }
 
@@ -159,7 +158,7 @@
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     protected void replaceEnterpriseStringSummary(
             PreferenceScreen screen, String preferenceKey, String overrideKey, int resource) {
-        if (!BuildCompat.isAtLeastT() || mDevicePolicyManager == null) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || mDevicePolicyManager == null) {
             return;
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index f07daa3..243403e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -48,8 +48,6 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settingslib.utils.BuildCompatUtils;
-
 /**
  * Converts the user avatar icon to a circularly clipped one with an optional badge and frame
  */
@@ -88,7 +86,7 @@
      * @return drawable containing just the badge
      */
     public static Drawable getManagedUserDrawable(Context context) {
-        if (BuildCompatUtils.isAtLeastT()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
             return getUpdatableManagedUserDrawable(context);
         } else {
             return getDrawableForDisplayDensity(
@@ -227,7 +225,7 @@
     }
 
     private static Drawable getManagementBadge(Context context) {
-        if (BuildCompatUtils.isAtLeastT()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
             return getUpdatableManagementBadge(context);
         } else {
             return getDrawableForDisplayDensity(
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepository.kt
index 4d25237..2952764 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/ZenModeRepository.kt
@@ -73,12 +73,12 @@
 
                 awaitClose { context.unregisterReceiver(receiver) }
             }
-            .apply {
+            .let {
                 if (Flags.volumePanelBroadcastFix()) {
-                    flowOn(backgroundCoroutineContext)
-                    stateIn(scope, SharingStarted.WhileSubscribed(), null)
+                    it.flowOn(backgroundCoroutineContext)
+                        .stateIn(scope, SharingStarted.WhileSubscribed(), null)
                 } else {
-                    shareIn(
+                    it.shareIn(
                         started = SharingStarted.WhileSubscribed(),
                         scope = scope,
                     )
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index a638df5..28bf348 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -22,11 +22,13 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
@@ -36,10 +38,13 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
 import android.util.Pair;
 
 import com.android.settingslib.widget.AdaptiveIcon;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -562,4 +567,77 @@
 
         assertThat(BluetoothUtils.isAvailableHearingDevice(mCachedBluetoothDevice)).isEqualTo(true);
     }
+
+    @Test
+    public void getGroupId_getCsipProfileId() {
+        when(mCachedBluetoothDevice.getGroupId()).thenReturn(1);
+
+        assertThat(BluetoothUtils.getGroupId(mCachedBluetoothDevice)).isEqualTo(1);
+    }
+
+    @Test
+    public void getGroupId_getLeAudioProfileId() {
+        when(mCachedBluetoothDevice.getGroupId())
+                .thenReturn(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        LeAudioProfile leAudio = mock(LeAudioProfile.class);
+        when(leAudio.getGroupId(mBluetoothDevice)).thenReturn(1);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(ImmutableList.of(leAudio));
+
+        assertThat(BluetoothUtils.getGroupId(mCachedBluetoothDevice)).isEqualTo(1);
+    }
+
+    @Test
+    public void getSecondaryDeviceForBroadcast_errorState_returnNull() {
+        assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+                .isNull();
+    }
+
+    @Test
+    public void getSecondaryDeviceForBroadcast_noSecondary_returnNull() {
+        Settings.Secure.putInt(
+                mContext.getContentResolver(),
+                BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+                1);
+        CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
+        when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
+        when(deviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getGroupId()).thenReturn(1);
+        BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+        when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(state));
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mBluetoothDevice));
+
+        assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+                .isNull();
+    }
+
+    @Test
+    public void getSecondaryDeviceForBroadcast_returnCorrectDevice() {
+        Settings.Secure.putInt(
+                mContext.getContentResolver(),
+                BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+                1);
+        CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
+        when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
+        CachedBluetoothDevice cachedBluetoothDevice = mock(CachedBluetoothDevice.class);
+        BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
+        when(cachedBluetoothDevice.getDevice()).thenReturn(bluetoothDevice);
+        when(cachedBluetoothDevice.getGroupId()).thenReturn(1);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getGroupId()).thenReturn(2);
+        when(deviceManager.findDevice(bluetoothDevice)).thenReturn(cachedBluetoothDevice);
+        when(deviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+        BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+        List<Long> bisSyncState = new ArrayList<>();
+        bisSyncState.add(1L);
+        when(state.getBisSyncState()).thenReturn(bisSyncState);
+        when(mAssistant.getAllSources(any(BluetoothDevice.class)))
+                .thenReturn(ImmutableList.of(state));
+        when(mAssistant.getAllConnectedDevices())
+                .thenReturn(ImmutableList.of(mBluetoothDevice, bluetoothDevice));
+
+        assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+                .isEqualTo(mCachedBluetoothDevice);
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 00fb7a1..2cdd0ae 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -80,6 +80,7 @@
                 Settings.System.SIP_RECEIVE_CALLS,
                 Settings.System.POINTER_SPEED,
                 Settings.System.POINTER_FILL_STYLE,
+                Settings.System.POINTER_STROKE_STYLE,
                 Settings.System.POINTER_SCALE,
                 Settings.System.VIBRATE_ON,
                 Settings.System.VIBRATE_WHEN_RINGING,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4235bc4..7b927d7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -30,6 +30,8 @@
 import static android.view.PointerIcon.LARGE_POINTER_SCALE;
 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BEGIN;
 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_END;
+import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_BEGIN;
+import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_END;
 
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -213,6 +215,9 @@
         VALIDATORS.put(System.POINTER_FILL_STYLE,
                 new InclusiveIntegerRangeValidator(POINTER_ICON_VECTOR_STYLE_FILL_BEGIN,
                         POINTER_ICON_VECTOR_STYLE_FILL_END));
+        VALIDATORS.put(System.POINTER_STROKE_STYLE,
+                new InclusiveIntegerRangeValidator(POINTER_ICON_VECTOR_STYLE_STROKE_BEGIN,
+                        POINTER_ICON_VECTOR_STYLE_STROKE_END));
         VALIDATORS.put(System.POINTER_SCALE,
                 new InclusiveFloatRangeValidator(DEFAULT_POINTER_SCALE, LARGE_POINTER_SCALE));
         VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 384cb7e..cd37ad1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2916,6 +2916,9 @@
                 Settings.System.POINTER_FILL_STYLE,
                 SystemSettingsProto.Pointer.POINTER_FILL_STYLE);
         dumpSetting(s, p,
+                Settings.System.POINTER_STROKE_STYLE,
+                SystemSettingsProto.Pointer.POINTER_STROKE_STYLE);
+        dumpSetting(s, p,
                 Settings.System.POINTER_SCALE,
                 SystemSettingsProto.Pointer.POINTER_SCALE);
         p.end(pointerToken);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b5776e2..469b9ce 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -23,7 +23,10 @@
 // See: http://go/android-license-faq
 license {
     name: "frameworks_base_packages_SystemUI_license",
-    visibility: [":__subpackages__"],
+    visibility: [
+        ":__subpackages__",
+        "//development/samples/SceneTransitionLayoutDemo:__subpackages__",
+    ],
     license_kinds: [
         "SPDX-license-identifier-Apache-2.0",
     ],
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 18fbf77..6bbac45 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1052,13 +1052,6 @@
 }
 
 flag {
-  name: "glanceable_hub_gesture_handle"
-  namespace: "systemui"
-  description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub"
-  bug: "339667383"
-}
-
-flag {
   name: "glanceable_hub_allow_keyguard_when_dreaming"
   namespace: "systemui"
   description: "Allows users to exit dream to keyguard with glanceable hub enabled"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index bb76c1d..cc4e775 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -9,21 +9,14 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.drawBehind
@@ -48,7 +41,6 @@
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.Flags
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -156,8 +148,6 @@
     val currentSceneKey: SceneKey by
         viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
     val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle()
-    val showGestureIndicator by
-        viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
     val backgroundType by
         viewModel.communalBackground.collectAsStateWithLifecycle(
             initialValue = CommunalBackgroundType.ANIMATED
@@ -200,19 +190,7 @@
                 )
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
-            // TODO(b/339667383): remove this temporary swipe gesture handle
-            Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) {
-                if (showGestureIndicator && Flags.glanceableHubGestureHandle()) {
-                    Box(
-                        modifier =
-                            Modifier.height(220.dp)
-                                .width(4.dp)
-                                .align(Alignment.CenterVertically)
-                                .background(color = Color.White, RoundedCornerShape(4.dp))
-                    )
-                    Spacer(modifier = Modifier.width(12.dp))
-                }
-            }
+            Box(modifier = Modifier.fillMaxSize())
         }
 
         scene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index be51c1a..97ed74f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -119,6 +119,7 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.onClick
@@ -871,7 +872,7 @@
             Icon(
                 imageVector = Icons.Outlined.Widgets,
                 contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
-                modifier = Modifier.size(Dimensions.IconSize),
+                modifier = Modifier.size(Dimensions.IconSize).clearAndSetSemantics {},
             )
             Spacer(modifier = Modifier.size(6.dp))
             Text(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
index 9f829cc..22c17f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
@@ -24,7 +24,9 @@
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.invalidateMeasurement
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 
 /**
@@ -32,14 +34,16 @@
  * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight].
  *
  * @param view Notification stack scroll view
- * @param padding extra padding in pixels to be added to the received content height.
+ * @param totalVerticalPadding extra padding to be added to the received stack content height.
  */
-fun Modifier.notificationStackHeight(view: NotificationScrollView, padding: Int = 0) =
-    this then StackLayoutElement(view, padding)
+fun Modifier.notificationStackHeight(
+    view: NotificationScrollView,
+    totalVerticalPadding: Dp = 0.dp,
+) = this then StackLayoutElement(view, totalVerticalPadding)
 
 private data class StackLayoutElement(
     val view: NotificationScrollView,
-    val padding: Int,
+    val padding: Dp,
 ) : ModifierNodeElement<StackLayoutNode>() {
 
     override fun create(): StackLayoutNode = StackLayoutNode(view, padding)
@@ -53,7 +57,7 @@
     }
 }
 
-private class StackLayoutNode(val view: NotificationScrollView, var padding: Int) :
+private class StackLayoutNode(val view: NotificationScrollView, var padding: Dp) :
     LayoutModifierNode, Modifier.Node() {
 
     private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() }
@@ -72,7 +76,7 @@
         measurable: Measurable,
         constraints: Constraints
     ): MeasureResult {
-        val contentHeight = padding + view.intrinsicStackHeight
+        val contentHeight = padding.roundToPx() + view.intrinsicStackHeight
         val placeable =
             measurable.measure(
                 constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index a568824..12ca997 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -289,11 +289,9 @@
         viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
     val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
 
-    val navBarHeightPx =
-        with(density) {
-            WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx().toInt()
-        }
-    val bottomPaddingPx = if (shouldReserveSpaceForNavBar) navBarHeightPx else 0
+    val topPadding = dimensionResource(id = R.dimen.notification_side_paddings)
+    val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
+    val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp
 
     val screenHeight = LocalRawScreenHeight.current
 
@@ -462,8 +460,12 @@
                             Modifier.nestedScroll(scrimNestedScrollConnection)
                         }
                         .verticalScroll(scrollState)
+                        .padding(top = topPadding)
                         .fillMaxWidth()
-                        .notificationStackHeight(view = stackScrollView, padding = bottomPaddingPx)
+                        .notificationStackHeight(
+                            view = stackScrollView,
+                            totalVerticalPadding = topPadding + bottomPadding,
+                        )
                         .onSizeChanged { size -> stackHeight.intValue = size.height },
             )
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index e8fdfc8..d95b388 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -913,6 +913,7 @@
     private val topOrLeftBehavior: NestedScrollBehavior,
     private val bottomOrRightBehavior: NestedScrollBehavior,
     private val isExternalOverscrollGesture: () -> Boolean,
+    private val pointersInfoOwner: PointersInfoOwner,
 ) {
     private val layoutState = layoutImpl.state
     private val draggableHandler = layoutImpl.draggableHandler(orientation)
@@ -924,25 +925,12 @@
         // moving on to the next scene.
         var canChangeScene = false
 
-        val actionUpOrLeft =
-            Swipe(
-                direction =
-                    when (orientation) {
-                        Orientation.Horizontal -> SwipeDirection.Left
-                        Orientation.Vertical -> SwipeDirection.Up
-                    },
-                pointerCount = 1,
-            )
-
-        val actionDownOrRight =
-            Swipe(
-                direction =
-                    when (orientation) {
-                        Orientation.Horizontal -> SwipeDirection.Right
-                        Orientation.Vertical -> SwipeDirection.Down
-                    },
-                pointerCount = 1,
-            )
+        var _lastPointersInfo: PointersInfo? = null
+        fun pointersInfo(): PointersInfo {
+            return checkNotNull(_lastPointersInfo) {
+                "PointersInfo should be initialized before the transition begins."
+            }
+        }
 
         fun hasNextScene(amount: Float): Boolean {
             val transitionState = layoutState.transitionState
@@ -950,8 +938,30 @@
             val fromScene = layoutImpl.scene(scene)
             val nextScene =
                 when {
-                    amount < 0f -> fromScene.userActions[actionUpOrLeft]
-                    amount > 0f -> fromScene.userActions[actionDownOrRight]
+                    amount < 0f -> {
+                        val actionUpOrLeft =
+                            Swipe(
+                                direction =
+                                    when (orientation) {
+                                        Orientation.Horizontal -> SwipeDirection.Left
+                                        Orientation.Vertical -> SwipeDirection.Up
+                                    },
+                                pointerCount = pointersInfo().pointersDown,
+                            )
+                        fromScene.userActions[actionUpOrLeft]
+                    }
+                    amount > 0f -> {
+                        val actionDownOrRight =
+                            Swipe(
+                                direction =
+                                    when (orientation) {
+                                        Orientation.Horizontal -> SwipeDirection.Right
+                                        Orientation.Vertical -> SwipeDirection.Down
+                                    },
+                                pointerCount = pointersInfo().pointersDown,
+                            )
+                        fromScene.userActions[actionDownOrRight]
+                    }
                     else -> null
                 }
             if (nextScene != null) return true
@@ -985,6 +995,8 @@
                     return@PriorityNestedScrollConnection false
                 }
 
+                _lastPointersInfo = pointersInfoOwner.pointersInfo()
+
                 // If the current swipe transition is *not* closed to 0f or 1f, then we want the
                 // scroll events to intercept the current transition to continue the scene
                 // transition.
@@ -1002,6 +1014,8 @@
                 val isZeroOffset =
                     if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
+                _lastPointersInfo = pointersInfoOwner.pointersInfo()
+
                 val canStart =
                     when (behavior) {
                         NestedScrollBehavior.DuringTransitionBetweenScenes -> {
@@ -1039,6 +1053,8 @@
                 // We could start an overscroll animation
                 canChangeScene = false
 
+                _lastPointersInfo = pointersInfoOwner.pointersInfo()
+
                 val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
                 if (canStart) {
                     isIntercepting = false
@@ -1049,10 +1065,11 @@
             canContinueScroll = { true },
             canScrollOnFling = false,
             onStart = { offsetAvailable ->
+                val pointersInfo = pointersInfo()
                 dragController =
                     draggableHandler.onDragStarted(
-                        pointersDown = 1,
-                        startedPosition = null,
+                        pointersDown = pointersInfo.pointersDown,
+                        startedPosition = pointersInfo.startedPosition,
                         overSlop = if (isIntercepting) 0f else offsetAvailable,
                     )
             },
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index f40f265..cdcfc84 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -29,18 +29,21 @@
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
+import androidx.compose.ui.input.pointer.changedToDown
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.input.pointer.positionChangeIgnoreConsumed
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.findNearestAncestor
 import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.IntSize
@@ -48,11 +51,12 @@
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastFirstOrNull
-import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastSumBy
 import kotlin.coroutines.cancellation.CancellationException
 import kotlin.math.sign
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
 
 /**
  * Make an element draggable in the given [orientation].
@@ -112,6 +116,18 @@
     }
 }
 
+private val TRAVERSE_KEY = Any()
+
+/** Find the nearest [PointersInfoOwner] ancestor or throw. */
+internal fun DelegatableNode.requireAncestorPointersInfoOwner(): PointersInfoOwner {
+    val ancestorNode =
+        checkNotNull(findNearestAncestor(TRAVERSE_KEY)) {
+            "This should never happen! Couldn't find a MultiPointerDraggableNode. " +
+                "Are we inside an SceneTransitionLayout?"
+        }
+    return ancestorNode as PointersInfoOwner
+}
+
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
     enabled: () -> Boolean,
@@ -120,15 +136,19 @@
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
     var swipeDetector: SwipeDetector = DefaultSwipeDetector,
 ) :
-    PointerInputModifierNode,
     DelegatingNode(),
+    PointerInputModifierNode,
     CompositionLocalConsumerModifierNode,
+    TraversableNode,
+    PointersInfoOwner,
     ObserverModifierNode {
     private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
     private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
     private val velocityTracker = VelocityTracker()
     private var previousEnabled: Boolean = false
 
+    override val traverseKey: Any = TRAVERSE_KEY
+
     var enabled: () -> Boolean = enabled
         set(value) {
             // Reset the pointer input whenever enabled changed.
@@ -185,12 +205,42 @@
         bounds: IntSize
     ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
 
+    private var startedPosition: Offset? = null
+    private var pointersDown: Int = 0
+
+    override fun pointersInfo(): PointersInfo {
+        return PointersInfo(
+            startedPosition = startedPosition,
+            // Note: We could have 0 pointers during fling or for other reasons.
+            pointersDown = pointersDown.coerceAtLeast(1),
+        )
+    }
+
     private suspend fun PointerInputScope.pointerInput() {
         if (!enabled()) {
             return
         }
 
         coroutineScope {
+            launch {
+                // Intercepts pointer inputs and exposes [PointersInfo], via
+                // [requireAncestorPointersInfoOwner], to our descendants.
+                awaitPointerEventScope {
+                    while (isActive) {
+                        // During the Initial pass, we receive the event after our ancestors.
+                        val pointers = awaitPointerEvent(PointerEventPass.Initial).changes
+
+                        pointersDown = pointers.countDown()
+                        if (pointersDown == 0) {
+                            // There are no more pointers down
+                            startedPosition = null
+                        } else if (startedPosition == null) {
+                            startedPosition = pointers.first().position
+                        }
+                    }
+                }
+            }
+
             awaitPointerEventScope {
                 while (isActive) {
                     try {
@@ -314,15 +364,16 @@
             }
 
         if (drag != null) {
-            // Count the number of pressed pointers.
-            val pressed = mutableSetOf<PointerId>()
-            currentEvent.changes.fastForEach { change ->
-                if (change.pressed) {
-                    pressed.add(change.id)
-                }
-            }
-
-            val controller = onDragStart(drag.position, overSlop, pressed.size)
+            val controller =
+                onDragStart(
+                    // The startedPosition is the starting position when a gesture begins (when the
+                    // first pointer touches the screen), not the point where we begin dragging.
+                    // For example, this could be different if one of our children intercepts the
+                    // gesture first and then we do.
+                    requireNotNull(startedPosition),
+                    overSlop,
+                    pointersDown,
+                )
 
             val successful: Boolean
             try {
@@ -364,12 +415,10 @@
         fun canBeConsumed(changes: List<PointerInputChange>): Boolean {
             // At least one pointer down AND
             return changes.fastAny { it.pressed } &&
-                // All pointers must be:
+                // All pointers must be either:
                 changes.fastAll {
-                    // A) recently pressed: even if the event has already been consumed, we can
-                    // still use the recently added finger event to determine whether to initiate
-                    // dragging the scene.
-                    it.changedToDownIgnoreConsumed() ||
+                    // A) unconsumed AND recently pressed
+                    it.changedToDown() ||
                         // B) unconsumed AND in a new position (on the current axis)
                         it.positionChange().toFloat() != 0f
                 }
@@ -461,4 +510,15 @@
             }
         }
     }
+
+    private fun List<PointerInputChange>.countDown() = fastSumBy { if (it.pressed) 1 else 0 }
 }
+
+internal fun interface PointersInfoOwner {
+    fun pointersInfo(): PointersInfo
+}
+
+internal data class PointersInfo(
+    val startedPosition: Offset?,
+    val pointersDown: Int,
+)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 1fa6b3f7..ddff2f7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -128,6 +128,7 @@
     bottomOrRightBehavior: NestedScrollBehavior,
     isExternalOverscrollGesture: () -> Boolean,
 ) : DelegatingNode() {
+    lateinit var pointersInfoOwner: PointersInfoOwner
     private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
         scenePriorityNestedScrollConnection(
             layoutImpl = layoutImpl,
@@ -135,6 +136,7 @@
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
+            pointersInfoOwner = { pointersInfoOwner.pointersInfo() }
         )
 
     private var nestedScrollNode: DelegatableNode =
@@ -144,6 +146,7 @@
         )
 
     override fun onAttach() {
+        pointersInfoOwner = requireAncestorPointersInfoOwner()
         delegate(nestedScrollNode)
     }
 
@@ -171,6 +174,7 @@
                 topOrLeftBehavior = topOrLeftBehavior,
                 bottomOrRightBehavior = bottomOrRightBehavior,
                 isExternalOverscrollGesture = isExternalOverscrollGesture,
+                pointersInfoOwner = pointersInfoOwner,
             )
         nestedScrollNode =
             nestedScrollModifierNode(
@@ -187,6 +191,7 @@
     topOrLeftBehavior: NestedScrollBehavior,
     bottomOrRightBehavior: NestedScrollBehavior,
     isExternalOverscrollGesture: () -> Boolean,
+    pointersInfoOwner: PointersInfoOwner,
 ) =
     NestedScrollHandlerImpl(
             layoutImpl = layoutImpl,
@@ -194,5 +199,6 @@
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
+            pointersInfoOwner = pointersInfoOwner,
         )
         .connection
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 65b388f..ff83d4b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -113,7 +113,10 @@
                     orientation = draggableHandler.orientation,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
-                    isExternalOverscrollGesture = { isExternalOverscrollGesture }
+                    isExternalOverscrollGesture = { isExternalOverscrollGesture },
+                    pointersInfoOwner = {
+                        PointersInfo(startedPosition = Offset.Zero, pointersDown = 1)
+                    }
                 )
                 .connection
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 2de6faa..1ae9992 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -839,6 +839,80 @@
     }
 
     @Test
+    fun elementTransitionDuringNestedScrollWith2Pointers() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        val translateY = 10.dp
+        val layoutWidth = 200.dp
+        val layoutHeight = 400.dp
+
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    initialScene = SceneA,
+                    transitions =
+                        transitions {
+                            from(SceneA, to = SceneB) {
+                                translate(TestElements.Foo, y = translateY)
+                            }
+                        },
+                )
+                    as MutableSceneTransitionLayoutStateImpl
+            }
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            SceneTransitionLayout(
+                state = state,
+                modifier = Modifier.size(layoutWidth, layoutHeight)
+            ) {
+                scene(
+                    SceneA,
+                    userActions = mapOf(Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB)
+                ) {
+                    Box(
+                        Modifier
+                            // Unconsumed scroll gesture will be intercepted by STL
+                            .verticalNestedScrollToScene()
+                            // A scrollable that does not consume the scroll gesture
+                            .scrollable(
+                                rememberScrollableState(consumeScrollDelta = { 0f }),
+                                Orientation.Vertical
+                            )
+                            .fillMaxSize()
+                    ) {
+                        Spacer(Modifier.element(TestElements.Foo).fillMaxSize())
+                    }
+                }
+                scene(SceneB) { Spacer(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(state.transitionState).isIdle()
+        val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag)
+        fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+        // Swipe down with 2 pointers by half of verticalSwipeDistance.
+        rule.onRoot().performTouchInput {
+            val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+            repeat(2) { i -> down(pointerId = i, middleTop) }
+            repeat(2) { i ->
+                // Scroll 50%
+                moveBy(
+                    pointerId = i,
+                    delta = Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f),
+                    delayMillis = 1_000,
+                )
+            }
+        }
+
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
+        fooElement.assertTopPositionInRootIsEqualTo(translateY * 0.5f)
+    }
+
+    @Test
     fun elementTransitionWithDistanceDuringOverscroll() {
         val layoutWidth = 200.dp
         val layoutHeight = 400.dp
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 460b640..ecafb17 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -128,7 +128,7 @@
         var started = false
         var dragged = false
         var stopped = false
-        var consumeBeforeMultiPointerDraggable = false
+        var consumedByDescendant = false
 
         var touchSlop = 0f
         rule.setContent {
@@ -138,6 +138,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
                         enabled = { true },
+                        // We want to start a drag gesture immediately
                         startDragImmediately = { true },
                         onDragStarted = { _, _, _ ->
                             started = true
@@ -157,7 +158,7 @@
                             awaitPointerEventScope {
                                 while (isActive) {
                                     val change = awaitPointerEvent().changes.first()
-                                    if (consumeBeforeMultiPointerDraggable) {
+                                    if (consumedByDescendant) {
                                         change.consume()
                                     }
                                 }
@@ -168,18 +169,19 @@
         }
 
         // The first part of the gesture is consumed by our descendant
-        consumeBeforeMultiPointerDraggable = true
+        consumedByDescendant = true
         rule.onRoot().performTouchInput {
             down(middle)
             moveBy(Offset(0f, touchSlop))
         }
 
-        started = false
-        dragged = false
-        stopped = false
+        // The events were consumed by our descendant, we should not start a drag gesture.
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
 
         // The next events could be consumed by us
-        consumeBeforeMultiPointerDraggable = false
+        consumedByDescendant = false
         rule.onRoot().performTouchInput {
             // The pointer is moved to a new position without reporting it
             updatePointerBy(0, Offset(0f, touchSlop))
@@ -188,7 +190,7 @@
             up()
         }
 
-        // This event should not be used to start a drag gesture
+        // The "up" event should not be used to start a drag gesture
         assertThat(started).isFalse()
         assertThat(dragged).isFalse()
         assertThat(stopped).isFalse()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index de6f1cc..41bf630 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -314,7 +314,6 @@
                         }
                     },
             )
-                as MutableSceneTransitionLayoutStateImpl
 
         // Default transition from A to B.
         assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
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 86a99e0..6412276 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -32,11 +32,9 @@
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Handler;
-import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.AttachedSurfaceControl;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
@@ -46,7 +44,6 @@
 
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController;
 import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
@@ -101,9 +98,6 @@
     ViewGroup mDreamOverlayContentView;
 
     @Mock
-    View mHubGestureIndicatorView;
-
-    @Mock
     Handler mHandler;
 
     @Mock
@@ -158,7 +152,6 @@
                 mDreamOverlayContainerView,
                 mComplicationHostViewController,
                 mDreamOverlayContentView,
-                mHubGestureIndicatorView,
                 mAmbientStatusBarViewController,
                 mLowLightTransitionCoordinator,
                 mTouchInsetSession,
@@ -179,18 +172,6 @@
                 mDreamManager);
     }
 
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
-    @Test
-    public void testHubGestureIndicatorGoneWhenFlagOff() {
-        verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE);
-    }
-
-    @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE})
-    @Test
-    public void testHubGestureIndicatorVisibleWhenFlagOn() {
-        verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE);
-    }
-
     @Test
     public void testRootSurfaceControlInsetSetOnAttach() {
         mController.onViewAttached();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
index 0f5e458..8914c80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
@@ -54,6 +55,9 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Transition
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -208,6 +212,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun showWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
         testScope.runTest {
             val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
@@ -245,6 +250,42 @@
 
     @Test
     @EnableSceneContainer
+    fun showWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone_scene_container() =
+        testScope.runTest {
+            val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+
+            powerInteractor.setAsleepForTest()
+
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING
+            )
+
+            powerInteractor.onCameraLaunchGestureDetected()
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                testScope = testScope,
+            )
+
+            assertThat(values)
+                .containsExactly(
+                    false,
+                )
+        }
+
+    @Test
+    @EnableSceneContainer
     fun occludingActivityWillDismissKeyguard() =
         testScope.runTest {
             val occludingActivityWillDismissKeyguard by
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 40918a4..a8eccc5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -19,7 +19,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.DisableSceneContainer
@@ -42,19 +41,15 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.Transition
-import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -67,36 +62,6 @@
     val repository = kosmos.fakeKeyguardTransitionRepository
     val testScope = kosmos.testScope
 
-    private val sceneTransitions =
-        MutableStateFlow<ObservableTransitionState>(
-            ObservableTransitionState.Idle(Scenes.Lockscreen)
-        )
-
-    private val lsToGone =
-        ObservableTransitionState.Transition(
-            Scenes.Lockscreen,
-            Scenes.Gone,
-            flowOf(Scenes.Lockscreen),
-            flowOf(0f),
-            false,
-            flowOf(false)
-        )
-
-    private val goneToLs =
-        ObservableTransitionState.Transition(
-            Scenes.Gone,
-            Scenes.Lockscreen,
-            flowOf(Scenes.Lockscreen),
-            flowOf(0f),
-            false,
-            flowOf(false)
-        )
-
-    @Before
-    fun setUp() {
-        kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
-    }
-
     @Test
     fun transitionCollectorsReceivesOnlyAppropriateEvents() =
         testScope.runTest {
@@ -318,7 +283,7 @@
     @Test
     fun isInTransitionToAnyState() =
         testScope.runTest {
-            val inTransition by collectValues(underTest.isInTransitionToAnyState)
+            val inTransition by collectValues(underTest.isInTransition)
 
             assertEquals(
                 listOf(
@@ -374,9 +339,50 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun isInTransition_withScene() =
+        testScope.runTest {
+            val inTransition by collectValues(underTest.isInTransition)
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
+                    false,
+                ),
+                inTransition
+            )
+
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Bouncer))
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+        }
+
+    @Test
     fun isInTransitionToAnyState_finishedStateIsStartedStateAfterCancels() =
         testScope.runTest {
-            val inTransition by collectValues(underTest.isInTransitionToAnyState)
+            val inTransition by collectValues(underTest.isInTransition)
 
             assertEquals(
                 listOf(
@@ -731,92 +737,6 @@
         }
 
     @Test
-    fun isInTransitionFromStateWhere() =
-        testScope.runTest {
-            val results by collectValues(underTest.isInTransitionFromStateWhere { it == DOZING })
-
-            sendSteps(
-                TransitionStep(AOD, DOZING, 0f, STARTED),
-                TransitionStep(AOD, DOZING, 0.5f, RUNNING),
-                TransitionStep(AOD, DOZING, 1f, FINISHED),
-            )
-
-            assertThat(results)
-                .isEqualTo(
-                    listOf(
-                        false,
-                    )
-                )
-
-            sendSteps(
-                TransitionStep(DOZING, GONE, 0f, STARTED),
-            )
-
-            assertThat(results)
-                .isEqualTo(
-                    listOf(
-                        false,
-                        true,
-                    )
-                )
-
-            sendSteps(
-                TransitionStep(DOZING, GONE, 0f, RUNNING),
-            )
-
-            assertThat(results)
-                .isEqualTo(
-                    listOf(
-                        false,
-                        true,
-                    )
-                )
-
-            sendSteps(
-                TransitionStep(DOZING, GONE, 0f, FINISHED),
-            )
-
-            assertThat(results)
-                .isEqualTo(
-                    listOf(
-                        false,
-                        true,
-                        false,
-                    )
-                )
-
-            sendSteps(
-                TransitionStep(GONE, DOZING, 0f, STARTED),
-                TransitionStep(GONE, DOZING, 0f, RUNNING),
-                TransitionStep(GONE, DOZING, 1f, FINISHED),
-            )
-
-            assertThat(results)
-                .isEqualTo(
-                    listOf(
-                        false,
-                        true,
-                        false,
-                    )
-                )
-
-            sendSteps(
-                TransitionStep(DOZING, GONE, 0f, STARTED),
-                TransitionStep(DOZING, GONE, 0f, RUNNING),
-            )
-
-            assertThat(results)
-                .isEqualTo(
-                    listOf(
-                        false,
-                        true,
-                        false,
-                        true,
-                    )
-                )
-        }
-
-    @Test
     fun isInTransitionWhere() =
         testScope.runTest {
             val results by
@@ -1599,7 +1519,7 @@
             val currentStatesConverted by
                 collectValues(underTest.transition(Edge.create(LOCKSCREEN, UNDEFINED)))
 
-            sceneTransitions.value = lsToGone
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
@@ -1615,7 +1535,7 @@
         testScope.runTest {
             val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE)))
 
-            sceneTransitions.value = goneToLs
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
@@ -1631,7 +1551,7 @@
             val currentStates by
                 collectValues(underTest.transition(Edge.create(LOCKSCREEN, DOZING)))
 
-            sceneTransitions.value = goneToLs
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
             val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
@@ -1648,7 +1568,7 @@
             val currentStatesReversed by
                 collectValues(underTest.transition(Edge.create(null, LOCKSCREEN)))
 
-            sceneTransitions.value = goneToLs
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
             val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
@@ -1666,7 +1586,7 @@
             val currentStates by collectValues(underTest.transition(Edge.create(null, UNDEFINED)))
             val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE)))
 
-            sceneTransitions.value = lsToGone
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
@@ -1683,7 +1603,7 @@
         testScope.runTest {
             val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE)))
 
-            sceneTransitions.value = goneToLs
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
index 8b49d43..601779f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
@@ -54,8 +54,10 @@
 import com.android.systemui.settings.userTracker
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+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.Test
@@ -103,9 +105,7 @@
             appName2,
         )
 
-    private val underTest: EditModeViewModel by lazy {
-        kosmos.editModeViewModel
-    }
+    private val underTest: EditModeViewModel by lazy { kosmos.editModeViewModel }
 
     @Before
     fun setUp() {
@@ -461,31 +461,87 @@
         }
 
     @Test
-    fun tileNotAvailable_notShowing() = with(kosmos) {
-        testScope.runTest {
-            val unavailableTile = "work"
-            qsTileFactory = FakeQSFactory { spec ->
-                FakeQSTile(userTracker.userId, spec != unavailableTile)
-            }
-            tileAvailabilityInteractorsMap = mapOf(
-                    unavailableTile to FakeTileAvailabilityInteractor(
-                            emptyMap<Int, Flow<Boolean>>().withDefault { flowOf(false) }
+    fun tileNotAvailable_notShowing() =
+        with(kosmos) {
+            testScope.runTest {
+                val unavailableTile = "work"
+                qsTileFactory = FakeQSFactory { spec ->
+                    FakeQSTile(userTracker.userId, spec != unavailableTile)
+                }
+                tileAvailabilityInteractorsMap =
+                    mapOf(
+                        unavailableTile to
+                            FakeTileAvailabilityInteractor(
+                                emptyMap<Int, Flow<Boolean>>().withDefault { flowOf(false) }
+                            )
                     )
-            )
-            val tiles by collectLastValue(underTest.tiles)
-            val currentTiles =
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
                     mutableListOf(
-                            TileSpec.create("flashlight"),
-                            TileSpec.create("airplane"),
-                            TileSpec.create("alarm"),
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create("alarm"),
                     )
-            currentTilesInteractor.setTiles(currentTiles)
+                currentTilesInteractor.setTiles(currentTiles)
 
-            underTest.startEditing()
+                underTest.startEditing()
 
-            assertThat(tiles!!.none { it.tileSpec == TileSpec.create(unavailableTile) }).isTrue()
+                assertThat(tiles!!.none { it.tileSpec == TileSpec.create(unavailableTile) })
+                    .isTrue()
+            }
         }
-    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun currentTiles_moveTileDown() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create("internet"),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                underTest.startEditing()
+                runCurrent()
+
+                // Move flashlight tile to index 3
+                underTest.addTile(TileSpec.create("flashlight"), 3)
+
+                assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec.spec })
+                    .containsExactly("airplane", "internet", "alarm", "flashlight")
+                    .inOrder()
+            }
+        }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun currentTiles_moveTileUp() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create("internet"),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                underTest.startEditing()
+                runCurrent()
+
+                // Move alarm tile to index 0
+                underTest.addTile(TileSpec.create("alarm"), 0)
+
+                assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec.spec })
+                    .containsExactly("alarm", "flashlight", "airplane", "internet")
+                    .inOrder()
+            }
+        }
 
     companion object {
         private val drawable1 = TestStubDrawable("drawable1")
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 5242fe3..3acb328 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
@@ -24,6 +24,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
@@ -62,6 +63,13 @@
 
     private val underTest = kosmos.sceneInteractor
 
+    init {
+        // Init lazy Fixtures. Accessing them once makes sure that the singletons are initialized
+        // and therefore starts to collect StateFlows eagerly (when there are any).
+        kosmos.deviceUnlockedInteractor
+        kosmos.keyguardEnabledInteractor
+    }
+
     @Test
     fun allSceneKeys() {
         assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
new file mode 100644
index 0000000..8a9720e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.NotificationChannel
+import android.app.NotificationChannel.NEWS_ID
+import android.app.NotificationChannel.PROMOTIONS_ID
+import android.app.NotificationChannel.RECS_ID
+import android.app.NotificationChannel.SOCIAL_MEDIA_ID
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class BundleCoordinatorTest : SysuiTestCase() {
+    @Mock private lateinit var newsController: NodeController
+    @Mock private lateinit var socialController: NodeController
+    @Mock private lateinit var recsController: NodeController
+    @Mock private lateinit var promoController: NodeController
+
+    private lateinit var coordinator: BundleCoordinator
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        coordinator =
+            BundleCoordinator(
+                newsController,
+                socialController,
+                recsController,
+                promoController
+            )
+    }
+
+    @Test
+    fun newsSectioner() {
+        assertThat(coordinator.newsSectioner.isInSection(makeEntryOfChannelType(NEWS_ID))).isTrue()
+        assertThat(coordinator.newsSectioner.isInSection(makeEntryOfChannelType("news"))).isFalse()
+    }
+
+    @Test
+    fun socialSectioner() {
+        assertThat(coordinator.socialSectioner.isInSection(makeEntryOfChannelType(SOCIAL_MEDIA_ID)))
+            .isTrue()
+        assertThat(coordinator.socialSectioner.isInSection(makeEntryOfChannelType("social")))
+            .isFalse()
+    }
+
+    @Test
+    fun recsSectioner() {
+        assertThat(coordinator.recsSectioner.isInSection(makeEntryOfChannelType(RECS_ID))).isTrue()
+        assertThat(coordinator.recsSectioner.isInSection(makeEntryOfChannelType("recommendations")))
+            .isFalse()
+    }
+
+    @Test
+    fun promoSectioner() {
+        assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType(PROMOTIONS_ID)))
+            .isTrue()
+        assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))).
+        isFalse()
+    }
+
+    private fun makeEntryOfChannelType(
+        type: String,
+        buildBlock: NotificationEntryBuilder.() -> Unit = {}
+    ): NotificationEntry {
+        val channel: NotificationChannel = NotificationChannel(type, type, 2)
+        val entry =
+            NotificationEntryBuilder()
+                .updateRanking {
+                    it.setChannel(channel)
+                }
+                .also(buildBlock)
+                .build()
+        return entry
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 6b5d072..495ab61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.policy
 
 import android.app.Notification
+import android.os.Handler
 import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -59,6 +60,9 @@
     // For creating TestableHeadsUpManager
     @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
     private val mUiEventLoggerFake = UiEventLoggerFake()
+
+    @Mock private lateinit var mBgHandler: Handler
+
     private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
     private val mGlobalSettings = FakeGlobalSettings()
     private val mSystemClock = FakeSystemClock()
@@ -78,7 +82,7 @@
 
         // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
         // declaration, where mocks are null
-        mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake)
+        mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler)
 
         testableHeadsUpManager =
             TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 206b39c..df07b44 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -38,6 +38,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.os.Handler;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
@@ -81,7 +82,7 @@
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
-
+    @Mock private Handler mBgHandler;
     @Mock private DumpManager dumpManager;
     private AvalancheController mAvalancheController;
 
@@ -148,7 +149,7 @@
     @Override
     public void SysuiSetup() throws Exception {
         super.SysuiSetup();
-        mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake);
+        mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 7346323..3d3438e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -104,8 +104,7 @@
                 UiEventLogger uiEventLogger,
                 JavaAdapter javaAdapter,
                 ShadeInteractor shadeInteractor,
-                AvalancheController avalancheController,
-                Handler bgHandler
+                AvalancheController avalancheController
         ) {
             super(
                     context,
@@ -123,8 +122,7 @@
                     uiEventLogger,
                     javaAdapter,
                     shadeInteractor,
-                    avalancheController,
-                    bgHandler
+                    avalancheController
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -147,8 +145,7 @@
                 mUiEventLogger,
                 mJavaAdapter,
                 mShadeInteractor,
-                mAvalancheController,
-                mBgHandler
+                mAvalancheController
         );
     }
 
@@ -173,7 +170,7 @@
         mContext.getOrCreateTestableResources().addOverride(
                 R.integer.ambient_notification_extension_time, 500);
 
-        mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger);
+        mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger, mBgHandler);
     }
 
     @Test
diff --git a/packages/SystemUI/res/drawable/hub_handle.xml b/packages/SystemUI/res/drawable/hub_handle.xml
deleted file mode 100644
index 8bc276f..0000000
--- a/packages/SystemUI/res/drawable/hub_handle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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">
-    <corners android:radius="4dp" />
-    <solid android:color="#FFFFFF" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index dcd3fa6..be1652b 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -21,19 +21,6 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <ImageView
-        android:id="@+id/glanceable_hub_handle"
-        android:layout_width="4dp"
-        android:layout_height="220dp"
-        android:layout_centerVertical="true"
-        android:layout_marginEnd="12dp"
-        android:background="@drawable/hub_handle"
-        android:visibility="gone"
-        android:contentDescription="UI indicator for swiping open the glanceable hub"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/dream_overlay_content"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
index 6032f0b..b33924c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
@@ -18,10 +18,10 @@
 
 import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.app.tracing.FlowTracing.tracedAwaitClose
+import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
 import dagger.Module
 import dagger.Provides
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
 
@@ -38,24 +38,28 @@
     }
 }
 
+private const val TAG = "AccessibilityRepository"
+
 private class AccessibilityRepositoryImpl(
     manager: AccessibilityManager,
 ) : AccessibilityRepository {
     override val isTouchExplorationEnabled: Flow<Boolean> =
-        conflatedCallbackFlow {
+        tracedConflatedCallbackFlow(TAG) {
                 val listener = TouchExplorationStateChangeListener(::trySend)
                 manager.addTouchExplorationStateChangeListener(listener)
                 trySend(manager.isTouchExplorationEnabled)
-                awaitClose { manager.removeTouchExplorationStateChangeListener(listener) }
+                tracedAwaitClose(TAG) {
+                    manager.removeTouchExplorationStateChangeListener(listener)
+                }
             }
             .distinctUntilChanged()
 
     override val isEnabled: Flow<Boolean> =
-        conflatedCallbackFlow {
+        tracedConflatedCallbackFlow(TAG) {
                 val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend)
                 manager.addAccessibilityStateChangeListener(listener)
                 trySend(manager.isEnabled)
-                awaitClose { manager.removeAccessibilityStateChangeListener(listener) }
+                tracedAwaitClose(TAG) { manager.removeAccessibilityStateChangeListener(listener) }
             }
             .distinctUntilChanged()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d6d40f2..b466f31 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -995,6 +995,16 @@
     }
 
     /**
+     * @return true if ultrasonic udfps HW is supported on this device. Can return true even if
+     * the user has not enrolled udfps. This may be false if called before
+     * onAllAuthenticatorsRegistered.
+     */
+    public boolean isUltrasonicUdfpsSupported() {
+        return getUdfpsProps() != null && !getUdfpsProps().isEmpty() && getUdfpsProps()
+                .get(0).isUltrasonicUdfps();
+    }
+
+    /**
      * @return true if sfps HW is supported on this device. Can return true even if the user has
      * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 9d3c6a4..3dd3758 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -270,6 +270,7 @@
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
                 @NonNull IUdfpsOverlayControllerCallback callback) {
+            mUdfpsOverlayInteractor.setRequestId(requestId);
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(
                         mContext,
@@ -404,6 +405,15 @@
                     handler::post,
                     authenticationCallback);
         }
+
+        /**
+         * Debug to run setIgnoreDisplayTouches
+         */
+        public void debugSetIgnoreDisplayTouches(boolean ignoreTouch) {
+            final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
+            UdfpsController.this.mFingerprintManager.setIgnoreDisplayTouches(
+                    requestId, mSensorProps.sensorId, ignoreTouch);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index f5e3d29..97ece11 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -75,6 +75,8 @@
             simFingerUp()
         } else if (args.size == 1 && args[0] == "biometricPrompt") {
             launchBiometricPrompt()
+        } else if (args.size == 2 && args[0] == "setIgnoreDisplayTouches") {
+            setIgnoreDisplayTouches(args[1].toBoolean())
         } else {
             invalidCommand(pw)
         }
@@ -186,6 +188,11 @@
         upEvent?.recycle()
     }
 
+    @VisibleForTesting
+    fun setIgnoreDisplayTouches(ignoreTouches: Boolean) {
+        udfpsOverlayController?.debugSetIgnoreDisplayTouches(ignoreTouches)
+    }
+
     private fun obtainMotionEvent(
         action: Int,
         x: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index a77cc1f..bb450c0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.content.Context
+import android.hardware.fingerprint.FingerprintManager
 import android.util.Log
 import android.view.MotionEvent
 import com.android.systemui.biometrics.AuthController
@@ -46,6 +47,7 @@
     @Application private val context: Context,
     private val authController: AuthController,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val fingerprintManager: FingerprintManager?,
     @Application scope: CoroutineScope
 ) {
     private fun calculateIconSize(): Int {
@@ -70,8 +72,25 @@
         return isUdfpsEnrolled && isWithinOverlayBounds
     }
 
+    private var _requestId = MutableStateFlow(0L)
+
+    /** RequestId of current AcquisitionClient */
+    val requestId: StateFlow<Long> = _requestId.asStateFlow()
+
+    fun setRequestId(requestId: Long) {
+        _requestId.value = requestId
+    }
+
     /** Sets whether Udfps overlay should handle touches */
     fun setHandleTouches(shouldHandle: Boolean = true) {
+        if (authController.isUltrasonicUdfpsSupported
+                && shouldHandle != _shouldHandleTouches.value) {
+            fingerprintManager?.setIgnoreDisplayTouches(
+                requestId.value,
+                authController.udfpsProps!!.get(0).sensorId,
+                !shouldHandle
+            )
+        }
         _shouldHandleTouches.value = shouldHandle
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index f991d5b..8270db1 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.brightness.ui.compose
 
-import androidx.compose.animation.core.animateIntAsState
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.size
@@ -57,12 +57,12 @@
 ) {
     var value by remember(gammaValue) { mutableIntStateOf(gammaValue) }
     val animatedValue by
-        animateIntAsState(targetValue = value, label = "BrightnessSliderAnimatedValue")
+        animateFloatAsState(targetValue = value.toFloat(), label = "BrightnessSliderAnimatedValue")
     val floatValueRange = valueRange.first.toFloat()..valueRange.last.toFloat()
-    val isRestricted = restriction is PolicyRestriction.Restricted
+    val isRestricted = remember(restriction) { restriction is PolicyRestriction.Restricted }
 
     PlatformSlider(
-        value = animatedValue.toFloat(),
+        value = animatedValue,
         valueRange = floatValueRange,
         enabled = !isRestricted,
         onValueChange = {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index f9f01f7..780bf70 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -322,14 +322,6 @@
         not(shadeInteractor.isAnyFullyExpanded)
             .stateIn(bgScope, SharingStarted.Eagerly, initialValue = false)
 
-    // TODO(b/339667383): remove this temporary swipe gesture handle
-    /**
-     * The dream overlay has its own gesture handle as the SysUI window is not visible above the
-     * dream. This flow will be false when dreaming so that we don't show a duplicate handle when
-     * opening the hub over the dream.
-     */
-    val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
-
     /** The type of background to use for the hub. */
     val communalBackground: Flow<CommunalBackgroundType> =
         communalSettingsInteractor.communalBackground
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 17202639..ed30d59 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -397,7 +397,7 @@
             Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
             Pair(
                 keyguardTransitionInteractor
-                    .isInTransitionToStateWhere(KeyguardState::deviceIsAsleepInState)
+                    .isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
                     .isFalse(),
                 "deviceNotTransitioningToAsleepState"
             ),
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 4fd1c24..76c7d23 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -21,8 +21,6 @@
 import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
-import static com.android.systemui.Flags.communalHub;
-import static com.android.systemui.Flags.glanceableHubGestureHandle;
 import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
 import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
@@ -192,7 +190,6 @@
             DreamOverlayContainerView containerView,
             ComplicationHostViewController complicationHostViewController,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
-            @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView,
             AmbientStatusBarViewController statusBarViewController,
             LowLightTransitionCoordinator lowLightTransitionCoordinator,
             TouchInsetManager.TouchInsetSession touchInsetSession,
@@ -230,12 +227,6 @@
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
                 R.dimen.dream_overlay_y_offset);
-
-        if (communalHub() && glanceableHubGestureHandle()) {
-            // TODO(b/339667383): remove this temporary swipe gesture handle
-            hubGestureIndicatorView.setVisibility(View.VISIBLE);
-        }
-
         final View view = mComplicationHostViewController.getView();
 
         mDreamOverlayContentView.addView(view,
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 76fcabd..12984efb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -18,7 +18,6 @@
 
 import android.content.res.Resources;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.lifecycle.Lifecycle;
@@ -42,7 +41,6 @@
 @Module
 public abstract class DreamOverlayModule {
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
-    public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
             "burn_in_protection_update_interval";
@@ -75,18 +73,6 @@
                 "R.id.dream_overlay_content must not be null");
     }
 
-    /**
-     * Gesture indicator bar on the right edge of the screen to indicate to users that they can
-     * swipe to see their widgets on lock screen.
-     */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
-    @Named(HUB_GESTURE_INDICATOR_VIEW)
-    public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) {
-        return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle),
-                "R.id.glanceable_hub_handle must not be null");
-    }
-
     /** */
     @Provides
     public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index f54fd22..b47fb65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -16,12 +16,18 @@
 
 package com.android.systemui.keyboard.shortcut.data.repository
 
+import android.content.Context
+import android.hardware.input.InputManager
+import android.util.Log
+import android.view.InputDevice
+import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
 import android.view.WindowManager
 import android.view.WindowManager.KeyboardShortcutsReceiver
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
 import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
@@ -30,29 +36,46 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 
 @SysUISingleton
 class ShortcutHelperCategoriesRepository
 @Inject
 constructor(
+    private val context: Context,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
     @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
     private val windowManager: WindowManager,
-    shortcutHelperStateRepository: ShortcutHelperStateRepository
+    private val inputManager: InputManager,
+    stateRepository: ShortcutHelperStateRepository
 ) {
 
-    val systemShortcutsCategory =
-        shortcutHelperStateRepository.state.map {
+    private val activeInputDevice =
+        stateRepository.state.map {
             if (it is Active) {
+                withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
+            } else {
+                null
+            }
+        }
+
+    val systemShortcutsCategory =
+        activeInputDevice.map {
+            if (it != null) {
                 toShortcutCategory(
-                    systemShortcutsSource.shortcutGroups(),
-                    ShortcutCategoryType.SYSTEM
+                    it.keyCharacterMap,
+                    SYSTEM,
+                    systemShortcutsSource.shortcutGroups()
                 )
             } else {
                 null
@@ -60,76 +83,128 @@
         }
 
     val multitaskingShortcutsCategory =
-        shortcutHelperStateRepository.state.map {
-            if (it is Active) {
-                toShortcutCategory(multitaskingShortcutsSource.shortcutGroups(), MULTI_TASKING)
+        activeInputDevice.map {
+            if (it != null) {
+                toShortcutCategory(
+                    it.keyCharacterMap,
+                    MULTI_TASKING,
+                    multitaskingShortcutsSource.shortcutGroups()
+                )
             } else {
                 null
             }
         }
 
     val imeShortcutsCategory =
-        shortcutHelperStateRepository.state.map {
-            if (it is Active) retrieveImeShortcuts(it.deviceId) else null
-        }
+        activeInputDevice.map { if (it != null) retrieveImeShortcuts(it) else null }
 
-    private suspend fun retrieveImeShortcuts(deviceId: Int): ShortcutCategory? {
+    private suspend fun retrieveImeShortcuts(
+        inputDevice: InputDevice,
+    ): ShortcutCategory? {
         return suspendCancellableCoroutine { continuation ->
             val shortcutsReceiver = KeyboardShortcutsReceiver { shortcutGroups ->
-                continuation.resumeWith(Result.success(toShortcutCategory(shortcutGroups, IME)))
+                continuation.resumeWith(
+                    Result.success(
+                        toShortcutCategory(inputDevice.keyCharacterMap, IME, shortcutGroups)
+                    )
+                )
             }
-            windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, deviceId)
+            windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, inputDevice.id)
         }
     }
 
     private fun toShortcutCategory(
-        shortcutGroups: List<KeyboardShortcutGroup>,
+        keyCharacterMap: KeyCharacterMap,
         type: ShortcutCategoryType,
+        shortcutGroups: List<KeyboardShortcutGroup>,
     ): ShortcutCategory? {
         val subCategories =
             shortcutGroups
                 .map { shortcutGroup ->
                     ShortcutSubCategory(
-                        label = shortcutGroup.label.toString(),
-                        shortcuts = toShortcuts(shortcutGroup.items)
+                        shortcutGroup.label.toString(),
+                        toShortcuts(keyCharacterMap, shortcutGroup.items)
                     )
                 }
                 .filter { it.shortcuts.isNotEmpty() }
         return if (subCategories.isEmpty()) {
+            Log.wtf(TAG, "Empty sub categories after converting $shortcutGroups")
             null
         } else {
             ShortcutCategory(type, subCategories)
         }
     }
 
-    private fun toShortcuts(infoList: List<KeyboardShortcutInfo>) =
-        infoList.mapNotNull { toShortcut(it) }
+    private fun toShortcuts(
+        keyCharacterMap: KeyCharacterMap,
+        infoList: List<KeyboardShortcutInfo>
+    ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it) }
 
-    private fun toShortcut(shortcutInfo: KeyboardShortcutInfo): Shortcut? {
-        val shortcutCommand = toShortcutCommand(shortcutInfo)
+    private fun toShortcut(
+        keyCharacterMap: KeyCharacterMap,
+        shortcutInfo: KeyboardShortcutInfo
+    ): Shortcut? {
+        val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo)
         return if (shortcutCommand == null) null
         else Shortcut(label = shortcutInfo.label!!.toString(), commands = listOf(shortcutCommand))
     }
 
-    private fun toShortcutCommand(info: KeyboardShortcutInfo): ShortcutCommand? {
-        val keyCodes = mutableListOf<Int>()
+    private fun toShortcutCommand(
+        keyCharacterMap: KeyCharacterMap,
+        info: KeyboardShortcutInfo
+    ): ShortcutCommand? {
+        val keys = mutableListOf<ShortcutKey>()
         var remainingModifiers = info.modifiers
         SUPPORTED_MODIFIERS.forEach { supportedModifier ->
             if ((supportedModifier and remainingModifiers) != 0) {
-                keyCodes += supportedModifier
+                keys += toShortcutKey(keyCharacterMap, supportedModifier) ?: return null
                 // "Remove" the modifier from the remaining modifiers
                 remainingModifiers = remainingModifiers and supportedModifier.inv()
             }
         }
         if (remainingModifiers != 0) {
             // There is a remaining modifier we don't support
+            Log.wtf(TAG, "Unsupported modifiers remaining: $remainingModifiers")
             return null
         }
-        keyCodes += info.keycode
-        return ShortcutCommand(keyCodes)
+        if (info.keycode != 0) {
+            keys += toShortcutKey(keyCharacterMap, info.keycode, info.baseCharacter) ?: return null
+        }
+        if (keys.isEmpty()) {
+            Log.wtf(TAG, "No keys for $info")
+            return null
+        }
+        return ShortcutCommand(keys)
+    }
+
+    private fun toShortcutKey(
+        keyCharacterMap: KeyCharacterMap,
+        keyCode: Int,
+        baseCharacter: Char = Char.MIN_VALUE,
+    ): ShortcutKey? {
+        val iconResId = ShortcutHelperKeys.keyIcons[keyCode]
+        if (iconResId != null) {
+            return ShortcutKey.Icon(iconResId)
+        }
+        if (baseCharacter > Char.MIN_VALUE) {
+            return ShortcutKey.Text(baseCharacter.toString())
+        }
+        val specialKeyLabel = ShortcutHelperKeys.specialKeyLabels[keyCode]
+        if (specialKeyLabel != null) {
+            val label = specialKeyLabel(context)
+            return ShortcutKey.Text(label)
+        }
+        val displayLabelCharacter = keyCharacterMap.getDisplayLabel(keyCode)
+        if (displayLabelCharacter.code != 0) {
+            return ShortcutKey.Text(displayLabelCharacter.toString())
+        }
+        Log.wtf(TAG, "Couldn't find label or icon for key: $keyCode")
+        return null
     }
 
     companion object {
+        private const val TAG = "SHCategoriesRepo"
+
         private val SUPPORTED_MODIFIERS =
             listOf(
                 KeyEvent.META_META_ON,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index e5b870a..adc6d95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -21,8 +21,8 @@
 class ShortcutBuilder(private val label: String) {
     val commands = mutableListOf<ShortcutCommand>()
 
-    fun command(vararg keyCodes: Int) {
-        commands += ShortcutCommand(keyCodes.toList())
+    fun command(builder: ShortcutCommandBuilder.() -> Unit) {
+        commands += ShortcutCommandBuilder().apply(builder).build()
     }
 
     fun build() = Shortcut(label, commands)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 3ac7fa8..5d05359 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -30,8 +30,8 @@
 class ShortcutCategoryBuilder(val type: ShortcutCategoryType) {
     private val subCategories = mutableListOf<ShortcutSubCategory>()
 
-    fun subCategory(label: String, shortcuts: List<Shortcut>) {
-        subCategories += ShortcutSubCategory(label, shortcuts)
+    fun subCategory(label: String, builder: ShortcutSubCategoryBuilder.() -> Unit) {
+        subCategories += ShortcutSubCategoryBuilder(label).apply(builder).build()
     }
 
     fun build() = ShortcutCategory(type, subCategories)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
index a98a8ff..e5b8096 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -16,4 +16,23 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
-data class ShortcutCommand(val keyCodes: List<Int>)
+import androidx.annotation.DrawableRes
+
+data class ShortcutCommand(val keys: List<ShortcutKey>)
+
+class ShortcutCommandBuilder {
+    private val keys = mutableListOf<ShortcutKey>()
+
+    fun key(text: String) {
+        keys += ShortcutKey.Text(text)
+    }
+
+    fun key(@DrawableRes drawableResId: Int) {
+        keys += ShortcutKey.Icon(drawableResId)
+    }
+
+    fun build() = ShortcutCommand(keys)
+}
+
+fun shortcutCommand(block: ShortcutCommandBuilder.() -> Unit) =
+    ShortcutCommandBuilder().apply(block).build()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
new file mode 100644
index 0000000..1abb78c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.keyboard.shortcut.shared.model
+
+import androidx.annotation.DrawableRes
+
+sealed interface ShortcutKey {
+    data class Text(val value: String) : ShortcutKey
+
+    data class Icon(@DrawableRes val drawableResId: Int) : ShortcutKey
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
index 4545b4c..1401678 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
@@ -17,3 +17,13 @@
 package com.android.systemui.keyboard.shortcut.shared.model
 
 data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>)
+
+class ShortcutSubCategoryBuilder(val label: String) {
+    private val shortcuts = mutableListOf<Shortcut>()
+
+    fun shortcut(label: String, builder: ShortcutBuilder.() -> Unit) {
+        shortcuts += ShortcutBuilder(label).apply(builder).build()
+    }
+
+    fun build() = ShortcutSubCategory(label, shortcuts)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 5f6fe1c..b32d0950 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -27,6 +27,7 @@
 import android.os.Handler
 import android.os.PowerManager
 import android.os.RemoteException
+import android.os.Trace
 import android.util.Log
 import android.view.RemoteAnimationTarget
 import android.view.SurfaceControl
@@ -385,10 +386,15 @@
                         valueAnimator.animatedValue as Float, openingWallpaperTargets)
             }
             addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationStart(animation: Animator) {
+                    super.onAnimationStart(animation)
+                    Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+                }
                 override fun onAnimationEnd(animation: Animator) {
                     Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
                     keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
                         false /* cancelled */)
+                    Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
                 }
             })
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
index e9bd889..e6bab4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
@@ -19,8 +19,9 @@
 
 import android.content.Context
 import android.os.UserHandle
+import com.android.app.tracing.FlowTracing.tracedAwaitClose
+import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-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.settings.UserTracker
@@ -28,7 +29,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -55,19 +55,20 @@
     private val userHandle: UserHandle,
 ) : KeyguardQuickAffordanceSelectionManager {
 
-    private val userId: Flow<Int> = conflatedCallbackFlow {
-        val callback =
-            object : UserTracker.Callback {
-                override fun onUserChanged(newUser: Int, userContext: Context) {
-                    trySendWithFailureLogging(newUser, TAG)
+    private val userId: Flow<Int> =
+        tracedConflatedCallbackFlow("userId") {
+            val callback =
+                object : UserTracker.Callback {
+                    override fun onUserChanged(newUser: Int, userContext: Context) {
+                        trySendWithFailureLogging(newUser, TAG)
+                    }
                 }
-            }
 
-        userTracker.addCallback(callback) { it.run() }
-        trySendWithFailureLogging(userTracker.userId, TAG)
+            userTracker.addCallback(callback) { it.run() }
+            trySendWithFailureLogging(userTracker.userId, TAG)
 
-        awaitClose { userTracker.removeCallback(callback) }
-    }
+            tracedAwaitClose("userId") { userTracker.removeCallback(callback) }
+        }
 
     private val clientOrNull: StateFlow<CustomizationProviderClient?> =
         userId
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index 1fe94d5..a145214 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -21,7 +21,6 @@
 import android.media.AudioManager
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Observer
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.ContentDescription
@@ -31,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.RingerModeTracker
@@ -86,8 +86,8 @@
                         audioManager.isVolumeFixed ->
                             ActivationState.NotSupported to R.string.volume_ringer_hint_mute
                         mode == AudioManager.RINGER_MODE_SILENT ->
-                            ActivationState.Active to R.string.volume_ringer_hint_mute
-                        else -> ActivationState.Inactive to R.string.volume_ringer_hint_unmute
+                            ActivationState.Active to R.string.volume_ringer_hint_unmute
+                        else -> ActivationState.Inactive to R.string.volume_ringer_hint_mute
                     }
 
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 63dd255..f837d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -62,6 +62,7 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
 interface KeyguardRepository {
@@ -362,30 +363,8 @@
 
     override val topClippingBounds = MutableStateFlow<Int?>(null)
 
-    override val isKeyguardShowing: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardStateController.Callback {
-                        override fun onKeyguardShowingChanged() {
-                            trySendWithFailureLogging(
-                                keyguardStateController.isShowing,
-                                TAG,
-                                "updated isKeyguardShowing"
-                            )
-                        }
-                    }
-
-                keyguardStateController.addCallback(callback)
-                // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(
-                    keyguardStateController.isShowing,
-                    TAG,
-                    "initial isKeyguardShowing"
-                )
-
-                awaitClose { keyguardStateController.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
+    override val isKeyguardShowing: MutableStateFlow<Boolean> =
+        MutableStateFlow(keyguardStateController.isShowing)
 
     private val _isAodAvailable = MutableStateFlow(false)
     override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable.asStateFlow()
@@ -394,91 +373,14 @@
         _isAodAvailable.value = value
     }
 
-    override val isKeyguardOccluded: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardStateController.Callback {
-                        override fun onKeyguardShowingChanged() {
-                            trySendWithFailureLogging(
-                                keyguardStateController.isOccluded,
-                                TAG,
-                                "updated isKeyguardOccluded"
-                            )
-                        }
-                    }
+    override val isKeyguardOccluded: MutableStateFlow<Boolean> =
+        MutableStateFlow(keyguardStateController.isOccluded)
 
-                keyguardStateController.addCallback(callback)
-                // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(
-                    keyguardStateController.isOccluded,
-                    TAG,
-                    "initial isKeyguardOccluded"
-                )
+    override val isKeyguardDismissible: MutableStateFlow<Boolean> =
+        MutableStateFlow(keyguardStateController.isUnlocked)
 
-                awaitClose { keyguardStateController.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
-
-    override val isKeyguardDismissible: StateFlow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardStateController.Callback {
-                        override fun onUnlockedChanged() {
-                            trySendWithFailureLogging(
-                                keyguardStateController.isUnlocked,
-                                TAG,
-                                "updated isKeyguardDismissible due to onUnlockedChanged"
-                            )
-                        }
-
-                        override fun onKeyguardShowingChanged() {
-                            trySendWithFailureLogging(
-                                keyguardStateController.isUnlocked,
-                                TAG,
-                                "updated isKeyguardDismissible due to onKeyguardShowingChanged"
-                            )
-                        }
-                    }
-
-                keyguardStateController.addCallback(callback)
-                // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(
-                    keyguardStateController.isUnlocked,
-                    TAG,
-                    "initial isKeyguardUnlocked"
-                )
-
-                awaitClose { keyguardStateController.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
-            .stateIn(
-                scope,
-                SharingStarted.Eagerly,
-                initialValue = false,
-            )
-
-    override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
-        val callback =
-            object : KeyguardStateController.Callback {
-                override fun onKeyguardGoingAwayChanged() {
-                    trySendWithFailureLogging(
-                        keyguardStateController.isKeyguardGoingAway,
-                        TAG,
-                        "updated isKeyguardGoingAway"
-                    )
-                }
-            }
-
-        keyguardStateController.addCallback(callback)
-        // Adding the callback does not send an initial update.
-        trySendWithFailureLogging(
-            keyguardStateController.isKeyguardGoingAway,
-            TAG,
-            "initial isKeyguardGoingAway"
-        )
-
-        awaitClose { keyguardStateController.removeCallback(callback) }
-    }
+    override val isKeyguardGoingAway: MutableStateFlow<Boolean> =
+        MutableStateFlow(keyguardStateController.isKeyguardGoingAway)
 
     private val _isKeyguardEnabled =
         MutableStateFlow(!lockPatternUtils.isLockScreenDisabled(userTracker.userId))
@@ -669,6 +571,35 @@
     private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
     override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
 
+    init {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onKeyguardShowingChanged() {
+                    isKeyguardShowing.value = keyguardStateController.isShowing
+                    isKeyguardOccluded.value = keyguardStateController.isOccluded
+                    isKeyguardDismissible.value = keyguardStateController.isUnlocked
+                }
+
+                override fun onUnlockedChanged() {
+                    isKeyguardDismissible.value = keyguardStateController.isUnlocked
+                }
+
+                override fun onKeyguardGoingAwayChanged() {
+                    isKeyguardGoingAway.value = keyguardStateController.isKeyguardGoingAway
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+
+        scope
+            .launch {
+                isKeyguardShowing.collect {
+                    // no-op to allow for callback removal
+                }
+            }
+            .invokeOnCompletion { keyguardStateController.removeCallback(callback) }
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 5573f0d..b44a8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -56,6 +56,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 2a9ee9f..868c462 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -44,6 +44,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index b423ed9..76e88a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -44,6 +45,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
@@ -99,10 +101,14 @@
                         biometricUnlockState,
                     ) ->
                     if (isWakeAndUnlock(biometricUnlockState.mode)) {
-                        startTransitionTo(
-                            KeyguardState.GONE,
-                            ownerReason = "biometric wake and unlock",
-                        )
+                        if (SceneContainerFlag.isEnabled) {
+                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                        } else {
+                            startTransitionTo(
+                                KeyguardState.GONE,
+                                ownerReason = "biometric wake and unlock",
+                            )
+                        }
                     }
                 }
         }
@@ -130,21 +136,35 @@
                         isIdleOnCommunal,
                         canTransitionToGoneOnWake,
                         primaryBouncerShowing) ->
-                    startTransitionTo(
-                        if (!deviceEntryRepository.isLockscreenEnabled()) {
-                            KeyguardState.GONE
-                        } else if (canTransitionToGoneOnWake) {
-                            KeyguardState.GONE
-                        } else if (primaryBouncerShowing) {
-                            KeyguardState.PRIMARY_BOUNCER
-                        } else if (occluded) {
-                            KeyguardState.OCCLUDED
-                        } else if (isIdleOnCommunal) {
-                            KeyguardState.GLANCEABLE_HUB
+                    if (!deviceEntryRepository.isLockscreenEnabled()) {
+                        if (SceneContainerFlag.isEnabled) {
+                            // TODO(b/336576536): Check if adaptation for scene framework is needed
                         } else {
-                            KeyguardState.LOCKSCREEN
+                            startTransitionTo(KeyguardState.GONE)
                         }
-                    )
+                    } else if (canTransitionToGoneOnWake) {
+                        if (SceneContainerFlag.isEnabled) {
+                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                        } else {
+                            startTransitionTo(KeyguardState.GONE)
+                        }
+                    } else if (primaryBouncerShowing) {
+                        if (SceneContainerFlag.isEnabled) {
+                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                        } else {
+                            startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+                        }
+                    } else if (occluded) {
+                        startTransitionTo(KeyguardState.OCCLUDED)
+                    } else if (isIdleOnCommunal) {
+                        if (SceneContainerFlag.isEnabled) {
+                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                        } else {
+                            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+                        }
+                    } else {
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
+                    }
                 }
         }
     }
@@ -176,23 +196,55 @@
                             // Handled by dismissFromDozing().
                             !isWakeAndUnlock(biometricUnlockState.mode)
                     ) {
-                        startTransitionTo(
-                            if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) {
-                                KeyguardState.GONE
-                            } else if (
-                                KeyguardWmStateRefactor.isEnabled &&
-                                    !deviceEntryRepository.isLockscreenEnabled()
-                            ) {
-                                KeyguardState.GONE
-                            } else if (primaryBouncerShowing) {
-                                KeyguardState.PRIMARY_BOUNCER
-                            } else if (isIdleOnCommunal) {
-                                KeyguardState.GLANCEABLE_HUB
+                        if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) {
+                            if (SceneContainerFlag.isEnabled) {
+                                // TODO(b/336576536): Check if adaptation for scene framework is
+                                // needed
                             } else {
-                                KeyguardState.LOCKSCREEN
-                            },
-                            ownerReason = "waking from dozing"
-                        )
+                                startTransitionTo(
+                                    KeyguardState.GONE,
+                                    ownerReason = "waking from dozing"
+                                )
+                            }
+                        } else if (
+                            KeyguardWmStateRefactor.isEnabled &&
+                                !deviceEntryRepository.isLockscreenEnabled()
+                        ) {
+                            if (SceneContainerFlag.isEnabled) {
+                                // TODO(b/336576536): Check if adaptation for scene framework is
+                                // needed
+                            } else {
+                                startTransitionTo(
+                                    KeyguardState.GONE,
+                                    ownerReason = "waking from dozing"
+                                )
+                            }
+                        } else if (primaryBouncerShowing) {
+                            if (SceneContainerFlag.isEnabled) {
+                                // TODO(b/336576536): Check if adaptation for scene framework is
+                                // needed
+                            } else {
+                                startTransitionTo(
+                                    KeyguardState.PRIMARY_BOUNCER,
+                                    ownerReason = "waking from dozing"
+                                )
+                            }
+                        } else if (isIdleOnCommunal) {
+                            if (SceneContainerFlag.isEnabled) {
+                                // TODO(b/336576536): Check if adaptation for scene framework is
+                                // needed
+                            } else {
+                                startTransitionTo(
+                                    KeyguardState.GLANCEABLE_HUB,
+                                    ownerReason = "waking from dozing"
+                                )
+                            }
+                        } else {
+                            startTransitionTo(
+                                KeyguardState.LOCKSCREEN,
+                                ownerReason = "waking from dozing"
+                            )
+                        }
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 47aa02a..117dbcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -41,6 +41,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7fa197c..0e76487 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -47,6 +47,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
@@ -90,8 +91,8 @@
 
     private fun listenForDreamingToGlanceableHub() {
         if (!communalHub()) return
-        if (SceneContainerFlag.isEnabled)
-            return // TODO(b/336576536): Check if adaptation for scene framework is needed
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
             glanceableHubTransitions.listenForGlanceableHubTransition(
                 transitionOwnerName = TAG,
@@ -102,6 +103,8 @@
     }
 
     private fun listenForDreamingToPrimaryBouncer() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .sample(startedKeyguardTransitionStep, ::Pair)
@@ -178,8 +181,8 @@
     }
 
     private fun listenForDreamingToGoneWhenDismissable() {
-        if (SceneContainerFlag.isEnabled)
-            return // TODO(b/336576536): Check if adaptation for scene framework is needed
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.isAbleToDream
                 .sampleCombine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index e516fa3..8041dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -50,6 +50,7 @@
     private val glanceableHubTransitions: GlanceableHubTransitions,
     keyguardInteractor: KeyguardInteractor,
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 07a2b04..b084824 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -45,6 +45,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 35a2d58..ff15a1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -57,6 +57,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
@@ -128,7 +129,7 @@
             keyguardInteractor.isAbleToDream
                 .filterRelevantKeyguardState()
                 .sampleCombine(
-                    transitionInteractor.currentTransitionInfoInternal,
+                    internalTransitionInteractor.currentTransitionInfoInternal,
                     finishedKeyguardState,
                     keyguardInteractor.isActiveDreamLockscreenHosted,
                 )
@@ -185,7 +186,7 @@
             shadeRepository.legacyShadeExpansion
                 .sampleCombine(
                     startedKeyguardTransitionStep,
-                    transitionInteractor.currentTransitionInfoInternal,
+                    internalTransitionInteractor.currentTransitionInfoInternal,
                     keyguardInteractor.statusBarState,
                     keyguardInteractor.isKeyguardDismissible,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 86d4cfb..84ca667 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -41,6 +41,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f23b12f..f98ed15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -50,6 +50,7 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
+    override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     @Background private val scope: CoroutineScope,
     @Background bgDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
new file mode 100644
index 0000000..a51421c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.annotation.FloatRange
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * This interactor provides direct access to [KeyguardTransitionRepository] internals and exposes
+ * functions to directly modify the transition state.
+ */
+@SysUISingleton
+class InternalKeyguardTransitionInteractor
+@Inject
+constructor(
+    private val repository: KeyguardTransitionRepository,
+) {
+
+    /**
+     * The [TransitionInfo] of the most recent call to
+     * [KeyguardTransitionRepository.startTransition].
+     *
+     * This should only be used by keyguard transition internals (From*TransitionInteractor and
+     * related classes). Other consumers of keyguard state in System UI should use
+     * [startedKeyguardState], [currentKeyguardState], and related flows.
+     *
+     * Keyguard internals use this to determine the most up-to-date KeyguardState that we've
+     * requested a transition to, even if the animator running the transition on the main thread has
+     * not yet emitted the STARTED TransitionStep.
+     *
+     * For example: if we're finished in GONE and press the power button twice very quickly, we may
+     * request a transition to AOD, but then receive the second power button press prior to the
+     * STARTED -> AOD transition step emitting. We still need the FromAodTransitionInteractor to
+     * request a transition from AOD -> LOCKSCREEN in response to the power press, even though the
+     * main thread animator hasn't emitted STARTED > AOD yet (which means [startedKeyguardState] is
+     * still GONE, which is not relevant to FromAodTransitionInteractor). In this case, the
+     * interactor can use this current transition info to determine that a STARTED -> AOD step
+     * *will* be emitted, and therefore that it can safely request an AOD -> LOCKSCREEN transition
+     * which will subsequently cancel GONE -> AOD.
+     */
+    internal val currentTransitionInfoInternal: StateFlow<TransitionInfo> =
+        repository.currentTransitionInfoInternal
+
+    suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
+
+    fun updateTransition(
+        transitionId: UUID,
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        state: TransitionState
+    ) = repository.updateTransition(transitionId, value, state)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 8d683f0..66efde1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -49,6 +49,7 @@
     val repository: KeyguardRepository,
     val biometricSettingsRepository: BiometricSettingsRepository,
     transitionInteractor: KeyguardTransitionInteractor,
+    internalTransitionInteractor: InternalKeyguardTransitionInteractor,
 ) {
 
     /**
@@ -74,7 +75,7 @@
             // Whenever the keyguard is disabled...
             .filter { enabled -> !enabled }
             .sampleCombine(
-                transitionInteractor.currentTransitionInfoInternal,
+                internalTransitionInteractor.currentTransitionInfoInternal,
                 biometricSettingsRepository.isCurrentUserInLockdown
             )
             .map { (_, transitionInfo, inLockdown) ->
@@ -93,7 +94,7 @@
                 .filter { enabled -> !enabled }
                 .sampleCombine(
                     biometricSettingsRepository.isCurrentUserInLockdown,
-                    transitionInteractor.currentTransitionInfoInternal,
+                    internalTransitionInteractor.currentTransitionInfoInternal,
                 )
                 .collect { (_, inLockdown, currentTransitionInfo) ->
                     if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
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 22ab82b..859326a 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
@@ -23,9 +23,10 @@
 import android.graphics.Point
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.FlowTracing.tracedAwaitClose
+import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -51,13 +52,13 @@
 import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -197,22 +198,22 @@
     val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
 
     /** Event for when the camera gesture is detected */
-    val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
-        val callback =
-            object : CommandQueue.Callbacks {
-                override fun onCameraLaunchGestureDetected(source: Int) {
-                    trySendWithFailureLogging(
-                        cameraLaunchSourceIntToModel(source),
-                        TAG,
-                        "updated onCameraLaunchGestureDetected"
-                    )
+    val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
+        tracedConflatedCallbackFlow("KeyguardInteractor#onCameraLaunchDetected") {
+            val callback =
+                object : CommandQueue.Callbacks {
+                    override fun onCameraLaunchGestureDetected(source: Int) {
+                        trySendWithFailureLogging(
+                            cameraLaunchSourceIntToModel(source),
+                            TAG,
+                            "updated onCameraLaunchGestureDetected")
+                    }
                 }
-            }
 
-        commandQueue.addCallback(callback)
+            commandQueue.addCallback(callback)
 
-        awaitClose { commandQueue.removeCallback(callback) }
-    }
+            tracedAwaitClose("onCameraLaunchDetected") { commandQueue.removeCallback(callback) }
+        }
 
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -250,17 +251,13 @@
 
     /** Keyguard can be clipped at the top as the shade is dragged */
     val topClippingBounds: Flow<Int?> by lazy {
-        combineTransform(
+        repository.topClippingBounds
+            .sampleFilter(
                 keyguardTransitionInteractor
                     .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
-                    .map { it == 1f }
-                    .onStart { emit(false) }
-                    .distinctUntilChanged(),
-                repository.topClippingBounds
-            ) { isGone, topClippingBounds ->
-                if (!isGone) {
-                    emit(topClippingBounds)
-                }
+                    .onStart { emit(0f) }
+            ) { goneValue ->
+                goneValue != 1f
             }
             .distinctUntilChanged()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index f385671..5af38ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
@@ -53,6 +54,7 @@
     private val repository: KeyguardOcclusionRepository,
     private val powerInteractor: PowerInteractor,
     private val transitionInteractor: KeyguardTransitionInteractor,
+    private val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     keyguardInteractor: KeyguardInteractor,
     deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
 ) {
@@ -78,7 +80,7 @@
         // *_BOUNCER -> LOCKSCREEN.
         return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
             KeyguardState.deviceIsAsleepInState(
-                transitionInteractor.currentTransitionInfoInternal.value.to
+                internalTransitionInteractor.currentTransitionInfoInternal.value.to
             )
     }
 
@@ -93,10 +95,15 @@
                 // currently
                 // GONE, in which case we're going back to GONE and launching the insecure camera).
                 powerInteractor.detailedWakefulness
-                    .sample(transitionInteractor.currentKeyguardState, ::Pair)
-                    .map { (wakefulness, currentKeyguardState) ->
-                        wakefulness.powerButtonLaunchGestureTriggered &&
-                            currentKeyguardState != KeyguardState.GONE
+                    .sample(
+                        transitionInteractor.isFinishedIn(
+                            Scenes.Gone,
+                            stateWithoutSceneContainer = KeyguardState.GONE
+                        ),
+                        ::Pair
+                    )
+                    .map { (wakefulness, isOnGone) ->
+                        wakefulness.powerButtonLaunchGestureTriggered && !isOnGone
                     },
                 // Emit false once that activity goes away.
                 isShowWhenLockedActivityOnTop.filter { !it }.map { false }
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 82255a0..bcbdc9c 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
@@ -19,9 +19,10 @@
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.kotlin.toPx
@@ -56,12 +57,18 @@
      */
     val viewParams: Flow<KeyguardSurfaceBehindModel> =
         combine(
-                transitionInteractor.startedKeyguardTransitionStep,
-                transitionInteractor.currentKeyguardState,
+                transitionInteractor.isInTransition(
+                    Edge.create(to = Scenes.Gone),
+                    edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
+                ),
+                transitionInteractor.isFinishedIn(
+                    Scenes.Gone,
+                    stateWithoutSceneContainer = KeyguardState.GONE
+                ),
                 notificationLaunchInteractor.isLaunchAnimationRunning,
-            ) { startedStep, currentState, notifAnimationRunning ->
+            ) { transitioningToGone, isOnGone, notifAnimationRunning ->
                 // If we're in transition to GONE, special unlock animation params apply.
-                if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
+                if (transitioningToGone) {
                     if (notifAnimationRunning) {
                         // If the notification launch animation is running, leave the alpha at 0f.
                         // The ActivityLaunchAnimator will morph it from the notification at the
@@ -87,14 +94,14 @@
                             animateFromTranslationY =
                                 SURFACE_TRANSLATION_Y_DISTANCE_DP.toPx(context).toFloat(),
                             translationY = 0f,
-                            startVelocity = swipeToDismissInteractor.dismissFling.value?.velocity
-                                    ?: 0f,
+                            startVelocity =
+                                swipeToDismissInteractor.dismissFling.value?.velocity ?: 0f,
                         )
                     }
                 }
 
                 // Default to the visibility of the current state, with no animations.
-                KeyguardSurfaceBehindModel(alpha = if (isSurfaceVisible(currentState)) 1f else 0f)
+                KeyguardSurfaceBehindModel(alpha = if (isOnGone) 1f else 0f)
             }
             .distinctUntilChanged()
 
@@ -103,10 +110,14 @@
      */
     private val isNotificationLaunchAnimationRunningOnKeyguard =
         notificationLaunchInteractor.isLaunchAnimationRunning
-            .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-            .map { (animationRunning, finishedState) ->
-                animationRunning && finishedState != KeyguardState.GONE
-            }
+            .sample(
+                transitionInteractor.isFinishedIn(
+                    Scenes.Gone,
+                    stateWithoutSceneContainer = KeyguardState.GONE
+                ),
+                ::Pair
+            )
+            .map { (animationRunning, isOnGone) -> animationRunning && !isOnGone }
             .onStart { emit(false) }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
index 5ad7762..805dbb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -22,7 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -40,6 +43,7 @@
     val deviceEntryInteractor: DeviceEntryInteractor,
     val deviceProvisioningInteractor: DeviceProvisioningInteractor,
     val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     val repository: KeyguardTransitionRepository,
 ) : CoreStartable {
 
@@ -56,17 +60,7 @@
 
     override fun start() {
         scope.launch {
-            val state =
-                if (showLockscreenOnBoot.first()) {
-                    KeyguardState.LOCKSCREEN
-                } else {
-                    KeyguardState.GONE
-                }
-
-            if (
-                keyguardTransitionInteractor.currentTransitionInfoInternal.value.from !=
-                    KeyguardState.OFF
-            ) {
+            if (internalTransitionInteractor.currentTransitionInfoInternal.value.from != OFF) {
                 Log.e(
                     "KeyguardTransitionInteractor",
                     "showLockscreenOnBoot emitted, but we've already " +
@@ -74,7 +68,23 @@
                         "transition, but this should not happen."
                 )
             } else {
-                repository.emitInitialStepsFromOff(state)
+                if (SceneContainerFlag.isEnabled) {
+                    // TODO(b/336576536): Some part of the transition implemented for flag off is
+                    //  missing here. There are two things achieved with this:
+                    //  1. Keyguard is hidden when the setup wizard is shown. This part is already
+                    //     implemented in scene container by disabling visibility instead of going
+                    //     to Gone. See [SceneContainerStartable.hydrateVisibility]. We might want
+                    //     to unify this logic here.
+                    //  2. When the auth method is set to NONE device boots into Gone (Launcher).
+                    //     For this we would just need to call changeScene(Scene.Gone).
+                    //     Unfortunately STL doesn't seem to be initialized at this point, therefore
+                    //     it needs a different solution.
+                    repository.emitInitialStepsFromOff(LOCKSCREEN)
+                } else {
+                    repository.emitInitialStepsFromOff(
+                        if (showLockscreenOnBoot.first()) LOCKSCREEN else GONE
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 2d389aa..f9bfaff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.annotation.FloatRange
 import android.annotation.SuppressLint
 import android.util.Log
 import com.android.compose.animation.scene.SceneKey
@@ -33,14 +32,12 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.pairwise
-import java.util.UUID
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,6 +47,7 @@
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -75,7 +73,7 @@
     private val fromAlternateBouncerTransitionInteractor:
         dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
     private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
-    private val sceneInteractor: dagger.Lazy<SceneInteractor>,
+    private val sceneInteractor: SceneInteractor,
 ) {
     private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
 
@@ -194,7 +192,7 @@
                 fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
 
                 return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) ||
-                    sceneInteractor.get().transitionState.value.isTransitioning(fromScene, toScene)
+                    sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
             }
         } else {
             flow
@@ -228,7 +226,7 @@
         stateWithoutSceneContainer: KeyguardState,
     ): Flow<Float> {
         return if (SceneContainerFlag.isEnabled) {
-            sceneInteractor.get().transitionProgress(scene)
+            sceneInteractor.transitionProgress(scene)
         } else {
             transitionValue(stateWithoutSceneContainer)
         }
@@ -383,33 +381,14 @@
             .distinctUntilChanged()
             .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
 
-    /**
-     * The [TransitionInfo] of the most recent call to
-     * [KeyguardTransitionRepository.startTransition].
-     *
-     * This should only be used by keyguard transition internals (From*TransitionInteractor and
-     * related classes). Other consumers of keyguard state in System UI should use
-     * [startedKeyguardState], [currentKeyguardState], and related flows.
-     *
-     * Keyguard internals use this to determine the most up-to-date KeyguardState that we've
-     * requested a transition to, even if the animator running the transition on the main thread has
-     * not yet emitted the STARTED TransitionStep.
-     *
-     * For example: if we're finished in GONE and press the power button twice very quickly, we may
-     * request a transition to AOD, but then receive the second power button press prior to the
-     * STARTED -> AOD transition step emitting. We still need the FromAodTransitionInteractor to
-     * request a transition from AOD -> LOCKSCREEN in response to the power press, even though the
-     * main thread animator hasn't emitted STARTED > AOD yet (which means [startedKeyguardState] is
-     * still GONE, which is not relevant to FromAodTransitionInteractor). In this case, the
-     * interactor can use this current transition info to determine that a STARTED -> AOD step
-     * *will* be emitted, and therefore that it can safely request an AOD -> LOCKSCREEN transition
-     * which will subsequently cancel GONE -> AOD.
-     */
-    internal val currentTransitionInfoInternal: StateFlow<TransitionInfo> =
-        repository.currentTransitionInfoInternal
-
-    /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
-    val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
+    val isInTransition =
+        combine(
+            isInTransitionWhere({ true }, { true }),
+            sceneInteractor.transitionState,
+        ) { isKeyguardTransitioning, sceneTransitionState ->
+            isKeyguardTransitioning ||
+                (SceneContainerFlag.isEnabled && sceneTransitionState.isTransitioning())
+        }
 
     /**
      * Called to start a transition that will ultimately dismiss the keyguard from the current
@@ -422,7 +401,7 @@
         // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
-        when (val startedState = currentTransitionInfoInternal.value.to) {
+        when (val startedState = repository.currentTransitionInfoInternal.value.to) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
             ALTERNATE_BOUNCER ->
@@ -448,7 +427,7 @@
     fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> {
         return if (SceneContainerFlag.isEnabled) {
                 if (edge.isSceneWildcardEdge()) {
-                    sceneInteractor.get().transitionState.map {
+                    sceneInteractor.transitionState.map {
                         when (edge) {
                             is Edge.StateToState ->
                                 throw IllegalStateException("Should not be reachable.")
@@ -469,30 +448,6 @@
     }
 
     /**
-     * Whether we're in a transition to a [KeyguardState] that matches the given predicate, but
-     * haven't yet completed it.
-     *
-     * If you only care about a single state, instead use the optimized [isInTransition].
-     */
-    fun isInTransitionToStateWhere(
-        stateMatcher: (KeyguardState) -> Boolean,
-    ): Flow<Boolean> {
-        return isInTransitionWhere(fromStatePredicate = { true }, toStatePredicate = stateMatcher)
-    }
-
-    /**
-     * Whether we're in a transition out of a [KeyguardState] that matches the given predicate, but
-     * haven't yet completed it.
-     *
-     * If you only care about a single state, instead use the optimized [isInTransition].
-     */
-    fun isInTransitionFromStateWhere(
-        stateMatcher: (KeyguardState) -> Boolean,
-    ): Flow<Boolean> {
-        return isInTransitionWhere(fromStatePredicate = stateMatcher, toStatePredicate = { true })
-    }
-
-    /**
      * Whether we're in a transition between two [KeyguardState]s that match the given predicates,
      * but haven't yet completed it.
      *
@@ -500,27 +455,15 @@
      * [isInTransition].
      */
     fun isInTransitionWhere(
-        fromStatePredicate: (KeyguardState) -> Boolean,
-        toStatePredicate: (KeyguardState) -> Boolean,
-    ): Flow<Boolean> {
-        return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
-    }
-
-    /**
-     * Whether we're in a transition between two [KeyguardState]s that match the given predicates,
-     * but haven't yet completed it.
-     *
-     * If you only care about a single state for both from and to, instead use the optimized
-     * [isInTransition].
-     */
-    private fun isInTransitionWhere(
-        fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
+        fromStatePredicate: (KeyguardState) -> Boolean = { true },
+        toStatePredicate: (KeyguardState) -> Boolean = { true },
     ): Flow<Boolean> {
         return repository.transitions
             .filter { it.transitionState != TransitionState.CANCELED }
             .mapLatest {
                 it.transitionState != TransitionState.FINISHED &&
-                    fromToStatePredicate(it.from, it.to)
+                    fromStatePredicate(it.from) &&
+                    toStatePredicate(it.to)
             }
             .distinctUntilChanged()
     }
@@ -532,9 +475,7 @@
 
     fun isFinishedIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
         return if (SceneContainerFlag.isEnabled) {
-            sceneInteractor
-                .get()
-                .transitionState
+            sceneInteractor.transitionState
                 .map { it.isIdle(scene) || it.isTransitioning(from = scene) }
                 .distinctUntilChanged()
         } else {
@@ -560,14 +501,6 @@
         return finishedKeyguardState.replayCache.last()
     }
 
-    suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
-
-    fun updateTransition(
-        transitionId: UUID,
-        @FloatRange(from = 0.0, to = 1.0) value: Float,
-        state: TransitionState
-    ) = repository.updateTransition(transitionId, value, state)
-
     companion object {
         private val TAG = KeyguardTransitionInteractor::class.simpleName
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index e148207..bbc3d76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import kotlinx.coroutines.CoroutineDispatcher
@@ -57,6 +58,8 @@
 ) {
     val name = this::class.simpleName ?: "UnknownTransitionInteractor"
     abstract val transitionRepository: KeyguardTransitionRepository
+    abstract val internalTransitionInteractor: InternalKeyguardTransitionInteractor
+
     abstract fun start()
 
     /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
@@ -79,14 +82,15 @@
         // a bugreport.
         ownerReason: String = "",
     ): UUID? {
-        if (fromState != transitionInteractor.currentTransitionInfoInternal.value.to) {
+        toState.checkValidState()
+        if (fromState != internalTransitionInteractor.currentTransitionInfoInternal.value.to) {
             Log.e(
                 name,
                 "Ignoring startTransition: This interactor asked to transition from " +
                     "$fromState -> $toState, but we last transitioned to " +
-                    "${transitionInteractor.currentTransitionInfoInternal.value.to}, not " +
-                    "$fromState. This should never happen - check currentTransitionInfoInternal " +
-                    "or use filterRelevantKeyguardState before starting transitions."
+                    "${internalTransitionInteractor.currentTransitionInfoInternal.value.to}, not" +
+                    " $fromState. This should never happen - check currentTransitionInfoInternal" +
+                    " or use filterRelevantKeyguardState before starting transitions."
             )
 
             if (fromState == transitionInteractor.finishedKeyguardState.replayCache.last()) {
@@ -164,6 +168,8 @@
      */
     @Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
     suspend fun maybeHandleInsecurePowerGesture(): Boolean {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return true
         if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
             if (keyguardInteractor.isKeyguardDismissible.value) {
                 startTransitionTo(
@@ -238,12 +244,11 @@
      * Whether we're in the KeyguardState relevant to this From*TransitionInteractor (which we know
      * from [fromState]).
      *
-     * This uses [KeyguardTransitionInteractor.currentTransitionInfoInternal], which is more up to
-     * date than [startedKeyguardState] as it does not wait for the emission of the first STARTED
-     * step.
+     * This uses [currentTransitionInfoInternal], which is more up to date than
+     * [startedKeyguardState] as it does not wait for the emission of the first STARTED step.
      */
     fun inOrTransitioningToRelevantKeyguardState(): Boolean {
-        return transitionInteractor.currentTransitionInfoInternal.value.to == fromState
+        return internalTransitionInteractor.currentTransitionInfoInternal.value.to == fromState
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 069f65b..3355ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -129,7 +129,7 @@
                     }
                 }
             } else {
-                transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition ->
+                transitionInteractor.isInTransition.flatMapLatest { isInTransition ->
                     if (!isInTransition) {
                         defaultSurfaceBehindVisibility
                     } else {
@@ -206,11 +206,11 @@
             transitionInteractor.currentKeyguardState
                 .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
                 .map { (currentState, startedWithPrev) ->
-                    val startedFromStep = startedWithPrev?.previousValue
-                    val startedStep = startedWithPrev?.newValue
+                    val startedFromStep = startedWithPrev.previousValue
+                    val startedStep = startedWithPrev.newValue
                     val returningToGoneAfterCancellation =
-                        startedStep?.to == KeyguardState.GONE &&
-                            startedFromStep?.transitionState == TransitionState.CANCELED &&
+                        startedStep.to == KeyguardState.GONE &&
+                            startedFromStep.transitionState == TransitionState.CANCELED &&
                             startedFromStep.from == KeyguardState.GONE
 
                     if (!returningToGoneAfterCancellation) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index 9b3ba7d..3248114 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository
 import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository.Companion.DEFAULT_STATE
+import com.android.systemui.keyguard.domain.interactor.InternalKeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
@@ -67,7 +68,8 @@
 class LockscreenSceneTransitionInteractor
 @Inject
 constructor(
-    val transitionInteractor: KeyguardTransitionInteractor,
+    private val transitionInteractor: KeyguardTransitionInteractor,
+    private val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
     @Application private val applicationScope: CoroutineScope,
     private val sceneInteractor: SceneInteractor,
     private val repository: LockscreenSceneTransitionRepository,
@@ -123,7 +125,7 @@
     }
 
     private fun finishCurrentTransition() {
-        transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+        internalTransitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
         resetTransitionData()
     }
 
@@ -131,13 +133,13 @@
         val newTransition =
             TransitionInfo(
                 ownerName = this::class.java.simpleName,
-                from = transitionInteractor.currentTransitionInfoInternal.value.to,
+                from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
                 to = state,
                 animator = null,
                 modeOnCanceled = TransitionModeOnCanceled.REVERSE
             )
-        currentTransitionId = transitionInteractor.startTransition(newTransition)
-        transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+        currentTransitionId = internalTransitionInteractor.startTransition(newTransition)
+        internalTransitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
         resetTransitionData()
     }
 
@@ -150,7 +152,8 @@
     private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
         if (transition.fromScene == Scenes.Lockscreen) {
             if (currentTransitionId != null) {
-                val currentToState = transitionInteractor.currentTransitionInfoInternal.value.to
+                val currentToState =
+                    internalTransitionInteractor.currentTransitionInfoInternal.value.to
                 if (currentToState == UNDEFINED) {
                     transitionKtfTo(transitionInteractor.getStartedFromState())
                 }
@@ -201,7 +204,7 @@
     }
 
     private suspend fun startTransitionFromLockscreen() {
-        val currentState = transitionInteractor.currentTransitionInfoInternal.value.to
+        val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
         val newTransition =
             TransitionInfo(
                 ownerName = this::class.java.simpleName,
@@ -217,12 +220,12 @@
         if (currentTransitionId != null) {
             resetTransitionData()
         }
-        currentTransitionId = transitionInteractor.startTransition(transitionInfo)
+        currentTransitionId = internalTransitionInteractor.startTransition(transitionInfo)
     }
 
     private fun updateProgress(progress: Float) {
         if (currentTransitionId == null) return
-        transitionInteractor.updateTransition(
+        internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             progress.coerceIn(0f, 1f),
             RUNNING
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 1c7b4d9..76f7749 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -38,7 +38,9 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.DisposableHandles
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
@@ -64,8 +66,9 @@
         falsingManager: FalsingManager,
         vibratorHelper: VibratorHelper,
         overrideColor: Color? = null,
-    ) {
+    ): DisposableHandle {
         DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
+        val disposables = DisposableHandles()
         val longPressHandlingView = view.longPressHandlingView
         val fgIconView = view.iconView
         val bgView = view.bgView
@@ -83,118 +86,125 @@
                 }
             }
 
-        view.repeatWhenAttached {
-            // Repeat on CREATED so that the view will always observe the entire
-            // GONE => AOD transition (even though the view may not be visible until the middle
-            // of the transition.
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch("$TAG#viewModel.isVisible") {
-                    viewModel.isVisible.collect { isVisible ->
-                        longPressHandlingView.isInvisible = !isVisible
+        disposables +=
+            view.repeatWhenAttached {
+                // Repeat on CREATED so that the view will always observe the entire
+                // GONE => AOD transition (even though the view may not be visible until the middle
+                // of the transition.
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch("$TAG#viewModel.isVisible") {
+                        viewModel.isVisible.collect { isVisible ->
+                            longPressHandlingView.isInvisible = !isVisible
+                        }
                     }
-                }
-                launch("$TAG#viewModel.isLongPressEnabled") {
-                    viewModel.isLongPressEnabled.collect { isEnabled ->
-                        longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+                    launch("$TAG#viewModel.isLongPressEnabled") {
+                        viewModel.isLongPressEnabled.collect { isEnabled ->
+                            longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+                        }
                     }
-                }
-                launch("$TAG#viewModel.isUdfpsSupported") {
-                    viewModel.isUdfpsSupported.collect { udfpsSupported ->
-                        longPressHandlingView.longPressDuration =
-                            if (udfpsSupported) {
-                                {
-                                    view.resources
-                                        .getInteger(R.integer.config_udfpsDeviceEntryIconLongPress)
-                                        .toLong()
+                    launch("$TAG#viewModel.isUdfpsSupported") {
+                        viewModel.isUdfpsSupported.collect { udfpsSupported ->
+                            longPressHandlingView.longPressDuration =
+                                if (udfpsSupported) {
+                                    {
+                                        view.resources
+                                            .getInteger(
+                                                R.integer.config_udfpsDeviceEntryIconLongPress
+                                            )
+                                            .toLong()
+                                    }
+                                } else {
+                                    {
+                                        view.resources
+                                            .getInteger(R.integer.config_lockIconLongPress)
+                                            .toLong()
+                                    }
+                                }
+                        }
+                    }
+                    launch("$TAG#viewModel.accessibilityDelegateHint") {
+                        viewModel.accessibilityDelegateHint.collect { hint ->
+                            view.accessibilityHintType = hint
+                            if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+                                view.setOnClickListener {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        HapticFeedbackConstants.CONFIRM,
+                                    )
+                                    applicationScope.launch { viewModel.onUserInteraction() }
                                 }
                             } else {
-                                {
-                                    view.resources
-                                        .getInteger(R.integer.config_lockIconLongPress)
-                                        .toLong()
-                                }
+                                view.setOnClickListener(null)
                             }
+                        }
                     }
-                }
-                launch("$TAG#viewModel.accessibilityDelegateHint") {
-                    viewModel.accessibilityDelegateHint.collect { hint ->
-                        view.accessibilityHintType = hint
-                        if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
-                            view.setOnClickListener {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    HapticFeedbackConstants.CONFIRM,
-                                )
-                                applicationScope.launch { viewModel.onUserInteraction() }
+                    launch("$TAG#viewModel.useBackgroundProtection") {
+                        viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
+                            if (useBackgroundProtection) {
+                                bgView.visibility = View.VISIBLE
+                            } else {
+                                bgView.visibility = View.GONE
                             }
-                        } else {
-                            view.setOnClickListener(null)
                         }
                     }
-                }
-                launch("$TAG#viewModel.useBackgroundProtection") {
-                    viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
-                        if (useBackgroundProtection) {
-                            bgView.visibility = View.VISIBLE
-                        } else {
-                            bgView.visibility = View.GONE
+                    launch("$TAG#viewModel.burnInOffsets") {
+                        viewModel.burnInOffsets.collect { burnInOffsets ->
+                            view.translationX = burnInOffsets.x.toFloat()
+                            view.translationY = burnInOffsets.y.toFloat()
+                            view.aodFpDrawable.progress = burnInOffsets.progress
                         }
                     }
-                }
-                launch("$TAG#viewModel.burnInOffsets") {
-                    viewModel.burnInOffsets.collect { burnInOffsets ->
-                        view.translationX = burnInOffsets.x.toFloat()
-                        view.translationY = burnInOffsets.y.toFloat()
-                        view.aodFpDrawable.progress = burnInOffsets.progress
-                    }
-                }
 
-                launch("$TAG#viewModel.deviceEntryViewAlpha") {
-                    viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
-                }
-            }
-        }
-
-        fgIconView.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                // Start with an empty state
-                fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
-                launch("$TAG#fpIconView.viewModel") {
-                    fgViewModel.viewModel.collect { viewModel ->
-                        fgIconView.setImageState(
-                            view.getIconState(viewModel.type, viewModel.useAodVariant),
-                            /* merge */ false
-                        )
-                        if (viewModel.type.contentDescriptionResId != -1) {
-                            fgIconView.contentDescription =
-                                fgIconView.resources.getString(
-                                    viewModel.type.contentDescriptionResId
-                                )
-                        }
-                        fgIconView.imageTintList =
-                            ColorStateList.valueOf(overrideColor?.toArgb() ?: viewModel.tint)
-                        fgIconView.setPadding(
-                            viewModel.padding,
-                            viewModel.padding,
-                            viewModel.padding,
-                            viewModel.padding,
-                        )
+                    launch("$TAG#viewModel.deviceEntryViewAlpha") {
+                        viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
                     }
                 }
             }
-        }
 
-        bgView.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch("$TAG#bgViewModel.alpha") {
-                    bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
-                }
-                launch("$TAG#bgViewModel.color") {
-                    bgViewModel.color.collect { color ->
-                        bgView.imageTintList = ColorStateList.valueOf(color)
+        disposables +=
+            fgIconView.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    // Start with an empty state
+                    fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
+                    launch("$TAG#fpIconView.viewModel") {
+                        fgViewModel.viewModel.collect { viewModel ->
+                            fgIconView.setImageState(
+                                view.getIconState(viewModel.type, viewModel.useAodVariant),
+                                /* merge */ false
+                            )
+                            if (viewModel.type.contentDescriptionResId != -1) {
+                                fgIconView.contentDescription =
+                                    fgIconView.resources.getString(
+                                        viewModel.type.contentDescriptionResId
+                                    )
+                            }
+                            fgIconView.imageTintList =
+                                ColorStateList.valueOf(overrideColor?.toArgb() ?: viewModel.tint)
+                            fgIconView.setPadding(
+                                viewModel.padding,
+                                viewModel.padding,
+                                viewModel.padding,
+                                viewModel.padding,
+                            )
+                        }
                     }
                 }
             }
-        }
+
+        disposables +=
+            bgView.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch("$TAG#bgViewModel.alpha") {
+                        bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
+                    }
+                    launch("$TAG#bgViewModel.color") {
+                        bgViewModel.color.collect { color ->
+                            bgView.imageTintList = ColorStateList.valueOf(color)
+                        }
+                    }
+                }
+            }
+
+        return disposables
     }
 }
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 e063380..ba9f018 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
@@ -37,7 +37,9 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.AodClockBurnInModel
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.util.kotlin.DisposableHandles
 import com.android.systemui.util.ui.value
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -56,85 +58,94 @@
         keyguardClockInteractor: KeyguardClockInteractor,
         blueprintInteractor: KeyguardBlueprintInteractor,
         rootViewModel: KeyguardRootViewModel,
-    ) {
-        keyguardRootView.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
+    ): DisposableHandle {
+        val disposables = DisposableHandles()
+        disposables +=
+            keyguardRootView.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
+                }
             }
-        }
 
-        keyguardRootView.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
-                    if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    viewModel.currentClock.collect { currentClock ->
-                        cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
-                        addClockViews(currentClock, keyguardRootView)
-                        updateBurnInLayer(keyguardRootView, viewModel, viewModel.clockSize.value)
-                        applyConstraints(clockSection, keyguardRootView, true)
-                    }
-                }
-
-                launch {
-                    if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    viewModel.clockSize.collect { clockSize ->
-                        updateBurnInLayer(keyguardRootView, viewModel, clockSize)
-                        blueprintInteractor.refreshBlueprint(Type.ClockSize)
-                    }
-                }
-
-                launch {
-                    if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    viewModel.clockShouldBeCentered.collect {
-                        viewModel.currentClock.value?.let {
-                            // TODO(b/301502635): remove "!it.config.useCustomClockScene" when
-                            // migrate clocks to blueprint is fully rolled out
-                            if (
-                                it.largeClock.config.hasCustomPositionUpdatedAnimation &&
-                                    !it.config.useCustomClockScene
-                            ) {
-                                blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
-                            } else {
-                                blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
-                            }
+        disposables +=
+            keyguardRootView.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                        viewModel.currentClock.collect { currentClock ->
+                            cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
+                            addClockViews(currentClock, keyguardRootView)
+                            updateBurnInLayer(
+                                keyguardRootView,
+                                viewModel,
+                                viewModel.clockSize.value
+                            )
+                            applyConstraints(clockSection, keyguardRootView, true)
                         }
                     }
-                }
 
-                launch {
-                    if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    combine(
-                            viewModel.hasAodIcons,
-                            rootViewModel.isNotifIconContainerVisible.map { it.value }
-                        ) { hasIcon, isVisible ->
-                            hasIcon && isVisible
+                    launch {
+                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                        viewModel.clockSize.collect { clockSize ->
+                            updateBurnInLayer(keyguardRootView, viewModel, clockSize)
+                            blueprintInteractor.refreshBlueprint(Type.ClockSize)
                         }
-                        .distinctUntilChanged()
-                        .collect { _ ->
+                    }
+
+                    launch {
+                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                        viewModel.clockShouldBeCentered.collect {
                             viewModel.currentClock.value?.let {
-                                if (it.config.useCustomClockScene) {
+                                // TODO(b/301502635): remove "!it.config.useCustomClockScene" when
+                                // migrate clocks to blueprint is fully rolled out
+                                if (
+                                    it.largeClock.config.hasCustomPositionUpdatedAnimation &&
+                                        !it.config.useCustomClockScene
+                                ) {
+                                    blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
+                                } else {
                                     blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
                                 }
                             }
                         }
-                }
+                    }
 
-                launch {
-                    if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                    rootViewModel.burnInModel.collect { burnInModel ->
-                        viewModel.currentClock.value?.let {
-                            it.largeClock.layout.applyAodBurnIn(
-                                AodClockBurnInModel(
-                                    translationX = burnInModel.translationX.toFloat(),
-                                    translationY = burnInModel.translationY.toFloat(),
-                                    scale = burnInModel.scale
+                    launch {
+                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                        combine(
+                                viewModel.hasAodIcons,
+                                rootViewModel.isNotifIconContainerVisible.map { it.value }
+                            ) { hasIcon, isVisible ->
+                                hasIcon && isVisible
+                            }
+                            .distinctUntilChanged()
+                            .collect { _ ->
+                                viewModel.currentClock.value?.let {
+                                    if (it.config.useCustomClockScene) {
+                                        blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+                                    }
+                                }
+                            }
+                    }
+
+                    launch {
+                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                        rootViewModel.burnInModel.collect { burnInModel ->
+                            viewModel.currentClock.value?.let {
+                                it.largeClock.layout.applyAodBurnIn(
+                                    AodClockBurnInModel(
+                                        translationX = burnInModel.translationX.toFloat(),
+                                        translationY = burnInModel.translationY.toFloat(),
+                                        scale = burnInModel.scale
+                                    )
                                 )
-                            )
+                            }
                         }
                     }
                 }
             }
-        }
+
+        return disposables
     }
 
     @VisibleForTesting
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 fc92afe..8f149fb 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
@@ -267,6 +267,23 @@
                             }
                         }
 
+                        launch {
+                            blueprintViewModel.currentTransition.collect { currentTransition ->
+                                // When blueprint/clock transitions end (null), make sure NSSL is in
+                                // the right place
+                                if (currentTransition == null) {
+                                    childViews[nsslPlaceholderId]?.let { notificationListPlaceholder
+                                        ->
+                                        viewModel.onNotificationContainerBoundsChanged(
+                                            notificationListPlaceholder.top.toFloat(),
+                                            notificationListPlaceholder.bottom.toFloat(),
+                                            animate = true,
+                                        )
+                                    }
+                                }
+                            }
+                        }
+
                         if (NotificationIconContainerRefactor.isEnabled) {
                             launch {
                                 val iconsAppearTranslationPx =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 191056c..8b74f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
+import kotlinx.coroutines.DisposableHandle
 
 object KeyguardSmartspaceViewBinder {
     @JvmStatic
@@ -39,8 +40,8 @@
         clockViewModel: KeyguardClockViewModel,
         smartspaceViewModel: KeyguardSmartspaceViewModel,
         blueprintInteractor: KeyguardBlueprintInteractor,
-    ) {
-        keyguardRootView.repeatWhenAttached {
+    ): DisposableHandle {
+        return keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") {
                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 0637bba..91e48b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.util.ui.value
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
 
 internal fun ConstraintSet.setVisibility(
     views: Iterable<View>,
@@ -70,21 +71,24 @@
     val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
     private val rootViewModel: KeyguardRootViewModel,
 ) : KeyguardSection() {
+    private var disposableHandle: DisposableHandle? = null
+
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
     override fun bindData(constraintLayout: ConstraintLayout) {
         if (!MigrateClocksToBlueprint.isEnabled) {
             return
         }
-
-        KeyguardClockViewBinder.bind(
-            this,
-            constraintLayout,
-            keyguardClockViewModel,
-            clockInteractor,
-            blueprintInteractor.get(),
-            rootViewModel,
-        )
+        disposableHandle?.dispose()
+        disposableHandle =
+            KeyguardClockViewBinder.bind(
+                this,
+                constraintLayout,
+                keyguardClockViewModel,
+                clockInteractor,
+                blueprintInteractor.get(),
+                rootViewModel,
+            )
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -97,7 +101,13 @@
         }
     }
 
-    override fun removeViews(constraintLayout: ConstraintLayout) {}
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        if (!MigrateClocksToBlueprint.isEnabled) {
+            return
+        }
+
+        disposableHandle?.dispose()
+    }
 
     private fun buildConstraints(
         clock: ClockController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index e01f0a1..51230dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -49,6 +49,7 @@
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Includes the device entry icon. */
@@ -70,6 +71,7 @@
     private val vibratorHelper: Lazy<VibratorHelper>,
 ) : KeyguardSection() {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
+    private var disposableHandle: DisposableHandle? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (
@@ -97,15 +99,17 @@
     override fun bindData(constraintLayout: ConstraintLayout) {
         if (DeviceEntryUdfpsRefactor.isEnabled) {
             constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
-                DeviceEntryIconViewBinder.bind(
-                    applicationScope,
-                    it,
-                    deviceEntryIconViewModel.get(),
-                    deviceEntryForegroundViewModel.get(),
-                    deviceEntryBackgroundViewModel.get(),
-                    falsingManager.get(),
-                    vibratorHelper.get(),
-                )
+                disposableHandle?.dispose()
+                disposableHandle =
+                    DeviceEntryIconViewBinder.bind(
+                        applicationScope,
+                        it,
+                        deviceEntryIconViewModel.get(),
+                        deviceEntryForegroundViewModel.get(),
+                        deviceEntryBackgroundViewModel.get(),
+                        falsingManager.get(),
+                        vibratorHelper.get(),
+                    )
             }
         } else {
             constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
@@ -178,6 +182,7 @@
     override fun removeViews(constraintLayout: ConstraintLayout) {
         if (DeviceEntryUdfpsRefactor.isEnabled) {
             constraintLayout.removeView(deviceEntryIconViewId)
+            disposableHandle?.dispose()
         } else {
             constraintLayout.removeView(R.id.lock_icon_view)
         }
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 8a751f0..55fc718 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
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
 
 @SysUISingleton
 open class SmartspaceSection
@@ -56,6 +57,7 @@
 
     private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
     private var pastVisibility: Int = -1
+    private var disposableHandle: DisposableHandle? = null
 
     override fun onRebuildBegin() {
         smartspaceController.suppressDisconnects = true
@@ -96,12 +98,14 @@
     override fun bindData(constraintLayout: ConstraintLayout) {
         if (!MigrateClocksToBlueprint.isEnabled) return
         if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
-        KeyguardSmartspaceViewBinder.bind(
-            constraintLayout,
-            keyguardClockViewModel,
-            keyguardSmartspaceViewModel,
-            blueprintInteractor.get(),
-        )
+        disposableHandle?.dispose()
+        disposableHandle =
+            KeyguardSmartspaceViewBinder.bind(
+                constraintLayout,
+                keyguardClockViewModel,
+                keyguardSmartspaceViewModel,
+                blueprintInteractor.get(),
+            )
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -188,6 +192,8 @@
         }
         smartspaceView?.viewTreeObserver?.removeOnGlobalLayoutListener(smartspaceVisibilityListener)
         smartspaceVisibilityListener = null
+
+        disposableHandle?.dispose()
     }
 
     private fun updateVisibility(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index c4383fc..680f966 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.FlowTracing.traceEmissionCount
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -229,7 +230,7 @@
                     )
                 }
                 .distinctUntilChanged()
-        }
+        }.traceEmissionCount({"QuickAfforcances#button${position.toSlotId()}"})
     }
 
     private fun KeyguardQuickAffordanceModel.toViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 988fe64..18a04ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -158,7 +158,7 @@
                 }
                 if (mp != null) {
                     if (DEBUG) {
-                        Log.d(mTag, "mPlayer.pause+release piid:" + player.getPlayerIId());
+                        Log.d(mTag, "mp.pause+release piid:" + mp.getPlayerIId());
                     }
                     mp.pause();
                     try {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index de490a5..311cbfb 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -96,12 +96,27 @@
                 logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" }
                 logDebug { "lockScreenState:isDefaultNotesAppSet=$isDefaultNotesAppSet" }
 
+                val isCustomLockScreenShortcutEnabled =
+                    context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)
+                val isShortcutSelectedOrDefaultEnabled =
+                    if (isCustomLockScreenShortcutEnabled) {
+                        isConfigSelected
+                    } else {
+                        isStylusEverUsed
+                    }
+                logDebug {
+                    "lockScreenState:isCustomLockScreenShortcutEnabled=" +
+                        isCustomLockScreenShortcutEnabled
+                }
+                logDebug {
+                    "lockScreenState:isShortcutSelectedOrDefaultEnabled=" +
+                        isShortcutSelectedOrDefaultEnabled
+                }
                 if (
                     isEnabled &&
                         isUserUnlocked &&
                         isDefaultNotesAppSet &&
-                        isConfigSelected &&
-                        isStylusEverUsed
+                        isShortcutSelectedOrDefaultEnabled
                 ) {
                     val contentDescription = ContentDescription.Resource(pickerNameResourceId)
                     val icon = Icon.Resource(pickerIconResourceId, contentDescription)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index c0371b9..9b4d10f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -47,7 +47,7 @@
     TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
         items(
             tiles.size,
-            key = { index -> sizedTiles[index].tile.spec },
+            key = { index -> sizedTiles[index].tile.spec.spec },
             span = { index -> GridItemSpan(sizedTiles[index].width) }
         ) { index ->
             Tile(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 19b8c66..62bfc72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,22 +39,20 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import javax.inject.Inject
-import javax.inject.Named
 
 @SysUISingleton
 @OptIn(ExperimentalCoroutinesApi::class)
 class EditModeViewModel
 @Inject
 constructor(
-        private val editTilesListInteractor: EditTilesListInteractor,
-        private val currentTilesInteractor: CurrentTilesInteractor,
-        private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
-        private val minTilesInteractor: MinimumTilesInteractor,
-        @Named("Default") private val defaultGridLayout: GridLayout,
-        @Application private val applicationScope: CoroutineScope,
-        gridLayoutTypeInteractor: GridLayoutTypeInteractor,
-        gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
+    private val editTilesListInteractor: EditTilesListInteractor,
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
+    private val minTilesInteractor: MinimumTilesInteractor,
+    @Named("Default") private val defaultGridLayout: GridLayout,
+    @Application private val applicationScope: CoroutineScope,
+    gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+    gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
 ) {
     private val _isEditing = MutableStateFlow(false)
 
@@ -93,10 +93,12 @@
                 val editTilesData = editTilesListInteractor.getTilesToEdit()
                 // Query only the non current platform tiles, as any current tile is clearly
                 // available
-                val unavailable = tilesAvailabilityInteractor.getUnavailableTiles(
-                        editTilesData.stockTiles.map { it.tileSpec }
-                                .minus(currentTilesInteractor.currentTilesSpecs.toSet())
-                )
+                val unavailable =
+                    tilesAvailabilityInteractor.getUnavailableTiles(
+                        editTilesData.stockTiles
+                            .map { it.tileSpec }
+                            .minus(currentTilesInteractor.currentTilesSpecs.toSet())
+                    )
                 currentTilesInteractor.currentTiles.map { tiles ->
                     val currentSpecs = tiles.map { it.spec }
                     val canRemoveTiles = currentSpecs.size > minimumTiles
@@ -106,28 +108,28 @@
                     val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs }
 
                     (currentTiles + nonCurrentTiles)
-                            .filterNot { it.tileSpec in unavailable }
-                            .map {
-                                val current = it.tileSpec in currentSpecs
-                                val availableActions = buildSet {
-                                    if (current) {
-                                        add(AvailableEditActions.MOVE)
-                                        if (canRemoveTiles) {
-                                            add(AvailableEditActions.REMOVE)
-                                        }
-                                    } else {
-                                        add(AvailableEditActions.ADD)
+                        .filterNot { it.tileSpec in unavailable }
+                        .map {
+                            val current = it.tileSpec in currentSpecs
+                            val availableActions = buildSet {
+                                if (current) {
+                                    add(AvailableEditActions.MOVE)
+                                    if (canRemoveTiles) {
+                                        add(AvailableEditActions.REMOVE)
                                     }
+                                } else {
+                                    add(AvailableEditActions.ADD)
                                 }
-                                EditTileViewModel(
-                                        it.tileSpec,
-                                        it.icon,
-                                        it.label,
-                                        it.appName,
-                                        current,
-                                        availableActions
-                                )
                             }
+                            EditTileViewModel(
+                                it.tileSpec,
+                                it.icon,
+                                it.label,
+                                it.appName,
+                                current,
+                                availableActions
+                            )
+                        }
                 }
             } else {
                 emptyFlow()
@@ -144,13 +146,16 @@
         _isEditing.value = false
     }
 
-    /** Immediately moves [tileSpec] to [position]. */
-    fun moveTile(tileSpec: TileSpec, position: Int) {
-        throw NotImplementedError("This is not supported yet")
-    }
-
-    /** Immediately adds [tileSpec] to the current tiles at [position]. */
+    /**
+     * Immediately adds [tileSpec] to the current tiles at [position]. If the [tileSpec] was already
+     * present, it will be moved to the new position.
+     */
     fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
+        // Removing tile if it's already present to insert it at the new index.
+        if (currentTilesInteractor.currentTilesSpecs.contains(tileSpec)) {
+            removeTile(tileSpec)
+        }
+
         currentTilesInteractor.addTile(tileSpec, position)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index ef01194..5885193 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.getValue
 import com.android.systemui.util.kotlin.pairwiseBy
 import dagger.Lazy
 import javax.inject.Inject
@@ -60,8 +61,8 @@
     private val repository: SceneContainerRepository,
     private val logger: SceneLogger,
     private val sceneFamilyResolvers: Lazy<Map<SceneKey, @JvmSuppressWildcards SceneResolver>>,
-    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
-    private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
+    private val deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
+    private val keyguardEnabledInteractor: Lazy<KeyguardEnabledInteractor>,
 ) {
 
     interface OnSceneAboutToChangeListener {
@@ -387,8 +388,8 @@
         val isChangeAllowed =
             to != Scenes.Gone ||
                 inMidTransitionFromGone ||
-                deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked ||
-                !keyguardEnabledInteractor.isKeyguardEnabled.value
+                deviceUnlockedInteractor.get().deviceUnlockStatus.value.isUnlocked ||
+                !keyguardEnabledInteractor.get().isKeyguardEnabled.value
         check(isChangeAllowed) {
             "Cannot change to the Gone scene while the device is locked and not currently" +
                 " transitioning from Gone. Current transition state is ${transitionState.value}." +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
index ca73081..ef96f43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -90,17 +91,22 @@
 
     /** Occlusion state to apply whenever a keyguard transition is FINISHED. */
     private val occlusionStateFromFinishedStep =
-        keyguardTransitionInteractor.finishedKeyguardTransitionStep
-            .sample(keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, ::Pair)
-            .map { (finishedStep, showWhenLockedOnTop) ->
+        combine(
+                keyguardTransitionInteractor.isFinishedIn(
+                    Scenes.Gone,
+                    stateWithoutSceneContainer = KeyguardState.GONE
+                ),
+                keyguardTransitionInteractor.isFinishedIn(KeyguardState.OCCLUDED),
+                keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+                ::Triple
+            )
+            .map { (isOnGone, isOnOccluded, showWhenLockedOnTop) ->
                 // If we're FINISHED in OCCLUDED, we want to render as occluded. We also need to
                 // remain occluded if a SHOW_WHEN_LOCKED activity is on the top of the task stack,
                 // and we're in any state other than GONE. This is necessary, for example, when we
                 // transition from OCCLUDED to a bouncer state. Otherwise, we should not be
                 // occluded.
-                val occluded =
-                    finishedStep.to == KeyguardState.OCCLUDED ||
-                        (showWhenLockedOnTop && finishedStep.to != KeyguardState.GONE)
+                val occluded = isOnOccluded || (showWhenLockedOnTop && !isOnGone)
                 OccludedState(occluded = occluded, animate = false)
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index f62b24a..3dcaff3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -21,6 +21,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
 import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
@@ -51,7 +52,8 @@
     }
 
     fun getNotificationBuckets(): IntArray {
-        if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled) {
+        if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled
+            || NotificationClassificationFlag.isEnabled) {
             // We don't need this list to be adaptive, it can be the superset of all features.
             return PriorityBucket.getAllInOrder()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationClassificationFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationClassificationFlag.kt
new file mode 100644
index 0000000..139347c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationClassificationFlag.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.statusbar.notification.collection
+
+import android.service.notification.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/**
+ * Helper for android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION
+ */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationClassificationFlag {
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_CLASSIFICATION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Are sections sorted by time? */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationClassification()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+            RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
new file mode 100644
index 0000000..244c594
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.NotificationChannel.NEWS_ID
+import android.app.NotificationChannel.PROMOTIONS_ID
+import android.app.NotificationChannel.RECS_ID
+import android.app.NotificationChannel.SOCIAL_MEDIA_ID
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.dagger.NewsHeader
+import com.android.systemui.statusbar.notification.dagger.PromoHeader
+import com.android.systemui.statusbar.notification.dagger.RecsHeader
+import com.android.systemui.statusbar.notification.dagger.SocialHeader
+import com.android.systemui.statusbar.notification.stack.BUCKET_NEWS
+import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO
+import com.android.systemui.statusbar.notification.stack.BUCKET_RECS
+import com.android.systemui.statusbar.notification.stack.BUCKET_SOCIAL
+import javax.inject.Inject
+
+/**
+ * Coordinator for sections derived from NotificationAssistantService classification.
+ */
+@CoordinatorScope
+class BundleCoordinator @Inject constructor(
+    @NewsHeader private val newsHeaderController: NodeController,
+    @SocialHeader private val socialHeaderController: NodeController,
+    @RecsHeader private val recsHeaderController: NodeController,
+    @PromoHeader private val promoHeaderController: NodeController,
+) : Coordinator {
+
+    val newsSectioner =
+            object : NotifSectioner("News", BUCKET_NEWS) {
+                override fun isInSection(entry: ListEntry): Boolean {
+                    return entry.representativeEntry?.channel?.id == NEWS_ID
+                }
+
+                override fun getHeaderNodeController(): NodeController? {
+                    return newsHeaderController
+                }
+            }
+
+    val socialSectioner =
+        object : NotifSectioner("Social", BUCKET_SOCIAL) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                return entry.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID
+            }
+
+            override fun getHeaderNodeController(): NodeController? {
+                return socialHeaderController
+            }
+        }
+
+    val recsSectioner =
+        object : NotifSectioner("Recommendations", BUCKET_RECS) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                return entry.representativeEntry?.channel?.id == RECS_ID
+            }
+
+            override fun getHeaderNodeController(): NodeController? {
+                return recsHeaderController
+            }
+        }
+
+    val promoSectioner =
+        object : NotifSectioner("Promotions", BUCKET_PROMO) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                return entry.representativeEntry?.channel?.id == PROMOTIONS_ID
+            }
+
+            override fun getHeaderNodeController(): NodeController? {
+                return promoHeaderController
+            }
+        }
+
+    override fun attach(pipeline: NotifPipeline) {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e413522..e038982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -17,10 +17,7 @@
 
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.PipelineDumpable
-import com.android.systemui.statusbar.notification.collection.PipelineDumper
-import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
+import com.android.systemui.statusbar.notification.collection.*
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
@@ -69,6 +66,7 @@
     dismissibilityCoordinator: DismissibilityCoordinator,
     dreamCoordinator: DreamCoordinator,
     statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
+    bundleCoordinator: BundleCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -132,6 +130,12 @@
             mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
         }
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
+        if (NotificationClassificationFlag.isEnabled) {
+            mOrderedSections.add(bundleCoordinator.newsSectioner);
+            mOrderedSections.add(bundleCoordinator.socialSectioner);
+            mOrderedSections.add(bundleCoordinator.recsSectioner);
+            mOrderedSections.add(bundleCoordinator.promoSectioner);
+        }
         mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
         mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index ca43591..e661090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -80,6 +80,50 @@
             .build()
 
     @Provides
+    @NewsHeader
+    @SysUISingleton
+    @JvmStatic fun providesNewsHeaderSubcomponent(
+        builder: Provider<SectionHeaderControllerSubcomponent.Builder>
+    ) = builder.get()
+        .nodeLabel("news header")
+        .headerText(com.android.internal.R.string.news_notification_channel_label)
+        .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
+        .build()
+
+    @Provides
+    @SocialHeader
+    @SysUISingleton
+    @JvmStatic fun providesSocialHeaderSubcomponent(
+        builder: Provider<SectionHeaderControllerSubcomponent.Builder>
+    ) = builder.get()
+        .nodeLabel("social header")
+        .headerText(com.android.internal.R.string.social_notification_channel_label)
+        .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
+        .build()
+
+    @Provides
+    @RecsHeader
+    @SysUISingleton
+    @JvmStatic fun providesRecsHeaderSubcomponent(
+        builder: Provider<SectionHeaderControllerSubcomponent.Builder>
+    ) = builder.get()
+        .nodeLabel("recs header")
+        .headerText(com.android.internal.R.string.recs_notification_channel_label)
+        .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
+        .build()
+
+    @Provides
+    @PromoHeader
+    @SysUISingleton
+    @JvmStatic fun providesPromoHeaderSubcomponent(
+        builder: Provider<SectionHeaderControllerSubcomponent.Builder>
+    ) = builder.get()
+        .nodeLabel("promo header")
+        .headerText(com.android.internal.R.string.promotional_notification_channel_label)
+        .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
+        .build()
+
+    @Provides
     @SilentHeader
     @JvmStatic fun providesSilentHeaderNodeController(
         @SilentHeader subcomponent: SectionHeaderControllerSubcomponent
@@ -126,6 +170,54 @@
     @JvmStatic fun providesIncomingHeaderController(
         @IncomingHeader subcomponent: SectionHeaderControllerSubcomponent
     ) = subcomponent.headerController
+
+    @Provides
+    @NewsHeader
+    @JvmStatic fun providesNewsHeaderNodeController(
+        @NewsHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.nodeController
+
+    @Provides
+    @NewsHeader
+    @JvmStatic fun providesNewsHeaderController(
+        @NewsHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.headerController
+
+    @Provides
+    @SocialHeader
+    @JvmStatic fun providesSocialHeaderNodeController(
+        @SocialHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.nodeController
+
+    @Provides
+    @SocialHeader
+    @JvmStatic fun providesSocialHeaderController(
+        @SocialHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.headerController
+
+    @Provides
+    @RecsHeader
+    @JvmStatic fun providesRecsHeaderNodeController(
+        @RecsHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.nodeController
+
+    @Provides
+    @RecsHeader
+    @JvmStatic fun providesRecsHeaderController(
+        @RecsHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.headerController
+
+    @Provides
+    @PromoHeader
+    @JvmStatic fun providesPromoHeaderNodeController(
+        @PromoHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.nodeController
+
+    @Provides
+    @PromoHeader
+    @JvmStatic fun providesPromoHeaderController(
+        @PromoHeader subcomponent: SectionHeaderControllerSubcomponent
+    ) = subcomponent.headerController
 }
 
 @Subcomponent(modules = [ SectionHeaderBindingModule::class ])
@@ -183,3 +275,19 @@
 @Scope
 @Retention(AnnotationRetention.BINARY)
 annotation class SectionHeaderScope
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class NewsHeader
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class SocialHeader
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class RecsHeader
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class PromoHeader
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 9e0dd8fc..1755123 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -20,9 +20,13 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_NEWS;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PRIORITY_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PROMO;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_RECS;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SOCIAL;
 
 import android.annotation.Nullable;
 import android.service.notification.StatusBarNotification;
@@ -135,6 +139,10 @@
                 return Notifications.Notification.SECTION_PEOPLE;
             case BUCKET_ALERTING: return Notifications.Notification.SECTION_ALERTING;
             case BUCKET_SILENT: return Notifications.Notification.SECTION_SILENT;
+            case BUCKET_NEWS: return Notifications.Notification.SECTION_NEWS;
+            case BUCKET_SOCIAL: return Notifications.Notification.SECTION_SOCIAL;
+            case BUCKET_RECS: return Notifications.Notification.SECTION_RECS;
+            case BUCKET_PROMO: return Notifications.Notification.SECTION_PROMO;
         }
         return Notifications.Notification.SECTION_UNKNOWN;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto
index c2ab275..ce4356a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto
@@ -43,6 +43,13 @@
         SECTION_ALERTING = 4;
         SECTION_SILENT = 5;
         SECTION_FOREGROUND_SERVICE = 6;
+        SECTION_PRIORITY_PEOPLE = 7;
+        SECTION_TOP_ONGOING = 8;
+        SECTION_TOP_UNSEEN = 9;
+        SECTION_NEWS = 10;
+        SECTION_SOCIAL = 11;
+        SECTION_RECS = 12;
+        SECTION_PROMO = 13;
     }
     optional NotificationSection section = 6;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
index fabb696..f4a4527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
@@ -20,6 +20,10 @@
             BUCKET_PRIORITY_PEOPLE,
             BUCKET_PEOPLE,
             BUCKET_ALERTING,
+            BUCKET_NEWS,
+            BUCKET_SOCIAL,
+            BUCKET_RECS,
+            BUCKET_PROMO,
             BUCKET_SILENT
         ]
 )
@@ -35,6 +39,10 @@
                 BUCKET_PRIORITY_PEOPLE,
                 BUCKET_PEOPLE,
                 BUCKET_ALERTING,
+                BUCKET_NEWS,
+                BUCKET_SOCIAL,
+                BUCKET_RECS,
+                BUCKET_PROMO,
                 BUCKET_SILENT,
             )
     }
@@ -49,4 +57,9 @@
 const val BUCKET_PRIORITY_PEOPLE = 7
 const val BUCKET_PEOPLE = 4
 const val BUCKET_ALERTING = 5
+const val BUCKET_NEWS = 10
+const val BUCKET_SOCIAL = 11
+const val BUCKET_RECS = 12
+const val BUCKET_PROMO = 13
 const val BUCKET_SILENT = 6
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 3400ad1..7441c70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -22,12 +22,10 @@
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.SourceType
+import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.dagger.AlertingHeader
-import com.android.systemui.statusbar.notification.dagger.IncomingHeader
-import com.android.systemui.statusbar.notification.dagger.PeopleHeader
-import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.dagger.*
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -51,7 +49,11 @@
     @IncomingHeader private val incomingHeaderController: SectionHeaderController,
     @PeopleHeader private val peopleHeaderController: SectionHeaderController,
     @AlertingHeader private val alertingHeaderController: SectionHeaderController,
-    @SilentHeader private val silentHeaderController: SectionHeaderController
+    @SilentHeader private val silentHeaderController: SectionHeaderController,
+    @NewsHeader private val newsHeaderController: SectionHeaderController,
+    @SocialHeader private val socialHeaderController: SectionHeaderController,
+    @RecsHeader private val recsHeaderController: SectionHeaderController,
+    @PromoHeader private val promoHeaderController: SectionHeaderController
 ) : SectionProvider {
 
     private val configurationListener =
@@ -84,6 +86,22 @@
     val mediaControlsView: MediaContainerView?
         get() = mediaContainerController.mediaContainerView
 
+    @VisibleForTesting
+    val newsHeaderView: SectionHeaderView?
+        get() = newsHeaderController.headerView
+
+    @VisibleForTesting
+    val socialHeaderView: SectionHeaderView?
+        get() = socialHeaderController.headerView
+
+    @VisibleForTesting
+    val recsHeaderView: SectionHeaderView?
+        get() = recsHeaderController.headerView
+
+    @VisibleForTesting
+    val promoHeaderView: SectionHeaderView?
+        get() = promoHeaderController.headerView
+
     /** Must be called before use. */
     fun initialize(parent: NotificationStackScrollLayout) {
         check(!initialized) { "NotificationSectionsManager already initialized" }
@@ -107,15 +125,24 @@
         incomingHeaderController.reinflateView(parent)
         mediaContainerController.reinflateView(parent)
         keyguardMediaController.attachSinglePaneContainer(mediaControlsView)
+        if (NotificationClassificationFlag.isEnabled) {
+            newsHeaderController.reinflateView(parent)
+            socialHeaderController.reinflateView(parent)
+            recsHeaderController.reinflateView(parent)
+            promoHeaderController.reinflateView(parent)
+        }
     }
 
     override fun beginsSection(view: View, previous: View?): Boolean =
         view === silentHeaderView ||
-            view === mediaControlsView ||
-            view === peopleHeaderView ||
-            view === alertingHeaderView ||
-            view === incomingHeaderView ||
-            getBucket(view) != getBucket(previous)
+                view === mediaControlsView ||
+                view === peopleHeaderView ||
+                view === alertingHeaderView ||
+                view === incomingHeaderView ||
+                (NotificationClassificationFlag.isEnabled && (view === newsHeaderView
+                        || view === socialHeaderView || view === recsHeaderView
+                        || view === promoHeaderView)) ||
+                getBucket(view) != getBucket(previous)
 
     private fun getBucket(view: View?): Int? =
         when {
@@ -124,6 +151,10 @@
             view === mediaControlsView -> BUCKET_MEDIA_CONTROLS
             view === peopleHeaderView -> BUCKET_PEOPLE
             view === alertingHeaderView -> BUCKET_ALERTING
+            view === newsHeaderView -> BUCKET_NEWS
+            view === socialHeaderView -> BUCKET_SOCIAL
+            view === recsHeaderView -> BUCKET_RECS
+            view === promoHeaderView -> BUCKET_PROMO
             view is ExpandableNotificationRow -> view.entry.bucket
             else -> null
         }
@@ -255,6 +286,12 @@
         peopleHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
         silentHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
         alertingHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+        if (NotificationClassificationFlag.isEnabled) {
+            newsHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+            socialHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+            recsHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+            promoHeaderView?.setForegroundColors(onSurface, onSurfaceVariant)
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index cb03334..dab3799 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2443,8 +2443,9 @@
         return count;
     }
 
-    private void updateContentHeight() {
-        final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
+    @VisibleForTesting
+    void updateContentHeight() {
+        final float scrimTopPadding = getScrimTopPaddingOrZero();
         final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
         final int footerIntrinsicHeight = mFooterView != null ? mFooterView.getIntrinsicHeight() : 0;
         final float height =
@@ -2949,7 +2950,7 @@
         return view.getHeight();
     }
 
-    public int getPositionInLinearLayout(View requestedView) {
+    private int getPositionInLinearLayout(View requestedView) {
         ExpandableNotificationRow childInGroup = null;
         ExpandableNotificationRow requestedRow = null;
         if (isChildInGroup(requestedView)) {
@@ -2958,7 +2959,7 @@
             childInGroup = (ExpandableNotificationRow) requestedView;
             requestedView = requestedRow = childInGroup.getNotificationParent();
         }
-        final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
+        final float scrimTopPadding = getScrimTopPaddingOrZero();
         int position = (int) scrimTopPadding;
         int visibleIndex = -1;
         ExpandableView lastVisibleChild = null;
@@ -2988,6 +2989,17 @@
         return 0;
     }
 
+    /**
+     * Returns the top scrim padding, or zero if the SceneContainer flag is enabled.
+     */
+    private int getScrimTopPaddingOrZero() {
+        if (SceneContainerFlag.isEnabled()) {
+            // the scrim padding is set on the notification placeholder
+            return 0;
+        }
+        return mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
+    }
+
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 58b72e5..36930bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1621,10 +1621,6 @@
         return mView.getTransientView(i);
     }
 
-    public int getPositionInLinearLayout(ExpandableView row) {
-        return mView.getPositionInLinearLayout(row);
-    }
-
     public NotificationStackScrollLayout getView() {
         return mView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index ee7b5c4..4282fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -487,11 +487,8 @@
         // expanded. Consider updating these states in updateContentView instead so that we don't
         // have to recalculate in every frame.
         float currentY = -ambientState.getScrollY();
-        if (!ambientState.isOnKeyguard()
-                || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
-            // add top padding at the start as long as we're not on the lock screen
-            currentY += mNotificationScrimPadding;
-        }
+        // add top padding at the start as long as we're not on the lock screen
+        currentY += getScrimTopPaddingOrZero(ambientState);
         state.firstViewInShelf = null;
         for (int i = 0; i < state.visibleChildren.size(); i++) {
             final ExpandableView view = state.visibleChildren.get(i);
@@ -548,11 +545,9 @@
      */
     protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
             AmbientState ambientState) {
-        if (!ambientState.isOnKeyguard()
-                || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
-            algorithmState.mCurrentYPosition += mNotificationScrimPadding;
-            algorithmState.mCurrentExpandedYPosition += mNotificationScrimPadding;
-        }
+        float scrimTopPadding = getScrimTopPaddingOrZero(ambientState);
+        algorithmState.mCurrentYPosition += scrimTopPadding;
+        algorithmState.mCurrentExpandedYPosition += scrimTopPadding;
 
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
@@ -580,9 +575,7 @@
                 && algorithmState.firstViewInShelf != null;
 
         final float shelfHeight = showingShelf ? ambientState.getShelf().getIntrinsicHeight() : 0f;
-        final float scrimPadding = ambientState.isOnKeyguard()
-                && (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding())
-                ? 0 : mNotificationScrimPadding;
+        final float scrimPadding = getScrimTopPaddingOrZero(ambientState);
 
         final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
         final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
@@ -594,6 +587,21 @@
         return stackHeight / stackEndHeight;
     }
 
+    /**
+     * Returns the top scrim padding, or zero if the SceneContainer flag is enabled.
+     */
+    private float getScrimTopPaddingOrZero(AmbientState ambientState) {
+        if (SceneContainerFlag.isEnabled()) {
+            // the scrim padding is set on the notification placeholder
+            return 0f;
+        }
+
+        boolean shouldUsePadding =
+                !ambientState.isOnKeyguard()
+                        || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding());
+        return shouldUsePadding ? mNotificationScrimPadding : 0f;
+    }
+
     private boolean hasNonClearableNotifs(StackScrollAlgorithmState algorithmState) {
         for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
             View child = algorithmState.visibleChildren.get(i);
@@ -664,6 +672,7 @@
         float viewEnd = stackTop + viewState.getYTranslation() + viewState.height;
         maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
                 view.mustStayOnScreen(),
+                // TODO(b/332574413) use the position from the HeadsUpNotificationPlaceholder
                 /* topVisible= */ viewState.getYTranslation() >= mNotificationScrimPadding,
                 viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
         );
@@ -891,6 +900,7 @@
                     // Ensure that the heads up is always visible even when scrolled off.
                     // NSSL y starts at top of screen in non-split-shade, but below the qs offset
                     // in split shade, so we only need to inset by the scrim padding in split shade.
+                    // TODO(b/332574413) get the clamp inset from HeadsUpNotificationPlaceholder
                     final float clampInset = ambientState.getUseSplitShade()
                             ? mNotificationScrimPadding : mQuickQsOffsetHeight;
                     clampHunToTop(clampInset, ambientState.getStackTranslation(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 1c57346..534d9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -186,7 +186,6 @@
         interactor.configurationBasedDimensions
             .map {
                 when {
-                    !it.useSplitShade -> 0
                     it.useLargeScreenHeader -> it.marginTopLargeScreen
                     else -> it.marginTop
                 }
@@ -372,7 +371,7 @@
                 paddingTopDimen,
                 interactor.topPosition
                     .sampleCombine(
-                        keyguardTransitionInteractor.isInTransitionToAnyState,
+                        keyguardTransitionInteractor.isInTransition,
                         shadeInteractor.qsExpansion,
                     )
                     .onStart { emit(Triple(0f, false, 0f)) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4ce9010..0623bb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -107,7 +107,6 @@
     private int mStatusBarState;
     private AnimationStateHandler mAnimationStateHandler;
 
-    private Handler mBgHandler;
     private int mHeadsUpInset;
 
     // Used for determining the region for touch interaction
@@ -152,8 +151,7 @@
             UiEventLogger uiEventLogger,
             JavaAdapter javaAdapter,
             ShadeInteractor shadeInteractor,
-            AvalancheController avalancheController,
-            @Background Handler bgHandler) {
+            AvalancheController avalancheController) {
         super(context, logger, handler, globalSettings, systemClock, executor,
                 accessibilityManagerWrapper, uiEventLogger, avalancheController);
         Resources resources = mContext.getResources();
@@ -163,7 +161,6 @@
         mGroupMembershipManager = groupMembershipManager;
         mVisualStabilityProvider = visualStabilityProvider;
         mAvalancheController = avalancheController;
-        mBgHandler = bgHandler;
         updateResources();
         configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
             @Override
@@ -405,11 +402,8 @@
             // Waiting HUNs in AvalancheController are still promoted to the HUN section and thus
             // seen in open shade; clear them so we don't show them again when the shade closes and
             // reordering is allowed again.
-            int waitingKeysSize = mAvalancheController.getWaitingKeys().size();
-            mBgHandler.post(() -> {
-                // Do this in the background to avoid missing frames when closing the shade
-                mAvalancheController.logDroppedHuns(waitingKeysSize);
-            });
+            final int numDropped = mAvalancheController.getWaitingKeys().size();
+            mAvalancheController.logDroppedHunsInBackground(numDropped);
             mAvalancheController.clearNext();
 
             // In open shade the first HUN is pinned, and visual stability logic prevents us from
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index dbe54f3..8aabdf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -15,12 +15,14 @@
  */
 package com.android.systemui.statusbar.policy
 
+import android.os.Handler
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
@@ -35,7 +37,10 @@
 @SysUISingleton
 class AvalancheController
 @Inject
-constructor(dumpManager: DumpManager, private val uiEventLogger: UiEventLogger) : Dumpable {
+constructor(dumpManager: DumpManager,
+            private val uiEventLogger: UiEventLogger,
+            @Background private val bgHandler: Handler
+) : Dumpable {
 
     private val tag = "AvalancheController"
     private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
@@ -315,7 +320,7 @@
 
         // Remove runnable labels for dropped huns
         val listToDrop = nextList.subList(1, nextList.size)
-        logDroppedHuns(listToDrop.size)
+        logDroppedHunsInBackground(listToDrop.size)
 
         if (debug) {
             // Clear runnable labels
@@ -332,10 +337,13 @@
         showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
     }
 
-    fun logDroppedHuns(numDropped: Int) {
-        for (n in 1..numDropped) {
-            uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
-        }
+    fun logDroppedHunsInBackground(numDropped: Int) {
+        bgHandler.post(Runnable {
+            // Do this in the background to avoid missing frames when closing the shade
+            for (n in 1..numDropped) {
+                uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+            }
+        })
     }
 
     fun clearNext() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java b/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
new file mode 100644
index 0000000..e13981d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * This is a background executor/dispatcher which guarantees that **within that instance**,
+ * operations will execute in the same order as they were dispatched.
+ * This is useful for background operations like register/unregister where ordering is important.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface SettingsSingleThreadBackground { }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index b836016..a03221e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
+import android.os.Handler
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -23,15 +24,16 @@
 import com.android.systemui.dagger.qualifiers.UiBackground
 import dagger.Module
 import dagger.Provides
-import java.util.concurrent.Executor
-import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.newFixedThreadPoolContext
 import kotlinx.coroutines.plus
+import java.util.concurrent.Executor
+import kotlin.coroutines.CoroutineContext
 
 private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
 
@@ -78,6 +80,14 @@
     }
 
     @Provides
+    @SysUISingleton
+    @SettingsSingleThreadBackground
+    fun settingsBgDispatcher(@Background bgHandler: Handler): CoroutineDispatcher {
+        // Handlers are guaranteed to be sequential so we use that one for now.
+        return bgHandler.asCoroutineDispatcher("SettingsBg")
+    }
+
+    @Provides
     @Background
     @SysUISingleton
     fun bgCoroutineContext(
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 4438763..816f55d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -23,7 +23,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
@@ -37,7 +37,7 @@
 
     @Inject
     GlobalSettingsImpl(ContentResolver contentResolver,
-            @Background CoroutineDispatcher bgDispatcher) {
+            @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) {
         mContentResolver = contentResolver;
         mBgDispatcher = bgDispatcher;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index 38ad5d0..9c98f43 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -22,13 +22,13 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.UserTracker;
-
-import kotlinx.coroutines.CoroutineDispatcher;
+import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 class SecureSettingsImpl implements SecureSettings {
     private final ContentResolver mContentResolver;
     private final UserTracker mUserTracker;
@@ -36,7 +36,7 @@
 
     @Inject
     SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker,
-            @Background CoroutineDispatcher bgDispatcher) {
+            @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) {
         mContentResolver = contentResolver;
         mUserTracker = userTracker;
         mBgDispatcher = bgDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 68cc753..406d95b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -22,13 +22,13 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.UserTracker;
-
-import kotlinx.coroutines.CoroutineDispatcher;
+import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 class SystemSettingsImpl implements SystemSettings {
     private final ContentResolver mContentResolver;
     private final UserTracker mUserTracker;
@@ -36,7 +36,7 @@
 
     @Inject
     SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker,
-            @Background CoroutineDispatcher bgDispatcher) {
+            @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) {
         mContentResolver = contentResolver;
         mUserTracker = userTracker;
         mBgCoroutineDispatcher = bgDispatcher;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 9a99ed7..1e3ee28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -27,6 +27,7 @@
 import android.hardware.biometrics.PromptInfo
 import android.hardware.biometrics.PromptVerticalListContentView
 import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.os.Handler
 import android.os.IBinder
@@ -88,9 +89,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
-
+import org.mockito.junit.MockitoJUnit
 
 private const val OP_PACKAGE_NAME = "biometric.testapp"
 
@@ -99,33 +99,21 @@
 @SmallTest
 open class AuthContainerViewTest : SysuiTestCase() {
 
-    @JvmField @Rule
-    var mockitoRule = MockitoJUnit.rule()
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
-    @Mock
-    lateinit var callback: AuthDialogCallback
-    @Mock
-    lateinit var userManager: UserManager
-    @Mock
-    lateinit var lockPatternUtils: LockPatternUtils
-    @Mock
-    lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock
-    lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector
-    @Mock
-    lateinit var windowToken: IBinder
-    @Mock
-    lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock
-    lateinit var vibrator: VibratorHelper
-    @Mock
-    lateinit var udfpsUtils: UdfpsUtils
-    @Mock
-    lateinit var authController: AuthController
-    @Mock
-    lateinit var selectedUserInteractor: SelectedUserInteractor
-    @Mock
-    private lateinit var packageManager: PackageManager
+    @Mock lateinit var callback: AuthDialogCallback
+    @Mock lateinit var userManager: UserManager
+    @Mock lateinit var fingerprintManager: FingerprintManager
+    @Mock lateinit var lockPatternUtils: LockPatternUtils
+    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector
+    @Mock lateinit var windowToken: IBinder
+    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock lateinit var vibrator: VibratorHelper
+    @Mock lateinit var udfpsUtils: UdfpsUtils
+    @Mock lateinit var authController: AuthController
+    @Mock lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
 
     private lateinit var displayRepository: FakeDisplayRepository
@@ -141,11 +129,12 @@
     private val fingerprintRepository = FakeFingerprintPropertyRepository()
     private val displayStateRepository = FakeDisplayStateRepository()
     private val credentialInteractor = FakeCredentialInteractor()
-    private val bpCredentialInteractor = PromptCredentialInteractor(
-        Dispatchers.Main.immediate,
-        biometricPromptRepository,
-        credentialInteractor,
-    )
+    private val bpCredentialInteractor =
+        PromptCredentialInteractor(
+            Dispatchers.Main.immediate,
+            biometricPromptRepository,
+            credentialInteractor,
+        )
     private val promptSelectorInteractor by lazy {
         PromptSelectorInteractorImpl(
             fingerprintRepository,
@@ -166,22 +155,26 @@
 
         displayStateInteractor =
             DisplayStateInteractorImpl(
-                    testScope.backgroundScope,
-                    mContext,
-                    fakeExecutor,
-                    displayStateRepository,
-                    displayRepository,
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                displayStateRepository,
+                displayRepository,
             )
         udfpsOverlayInteractor =
-                UdfpsOverlayInteractor(
-                        context,
-                        authController,
-                        selectedUserInteractor,
-                        testScope.backgroundScope,
-                )
+            UdfpsOverlayInteractor(
+                context,
+                authController,
+                selectedUserInteractor,
+                fingerprintManager,
+                testScope.backgroundScope,
+            )
         biometricStatusInteractor =
-                BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository,
-                    fingerprintRepository)
+            BiometricStatusInteractorImpl(
+                activityTaskManager,
+                biometricStatusRepository,
+                fingerprintRepository
+            )
         iconProvider = IconProvider(context)
         // Set up default logo icon
         whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
@@ -198,10 +191,8 @@
     @Test
     fun testNotifiesAnimatedIn() {
         initializeFingerprintContainer()
-        verify(callback).onDialogAnimatedIn(
-            authContainer?.requestId ?: 0L,
-            true /* startFingerprintNow */
-        )
+        verify(callback)
+            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
     }
 
     @Test
@@ -246,10 +237,8 @@
         waitForIdleSync()
 
         // attaching the view resets the state and allows this to happen again
-        verify(callback).onDialogAnimatedIn(
-            authContainer?.requestId ?: 0L,
-            true /* startFingerprintNow */
-        )
+        verify(callback)
+            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
     }
 
     @Test
@@ -274,10 +263,8 @@
 
         // the first time is triggered by initializeFingerprintContainer()
         // the second time was triggered by dismissWithoutCallback()
-        verify(callback, times(2)).onDialogAnimatedIn(
-            authContainer?.requestId ?: 0L,
-            true /* startFingerprintNow */
-        )
+        verify(callback, times(2))
+            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
     }
 
     @Test
@@ -288,18 +275,18 @@
         verify(panelInteractionDetector).disable()
     }
 
-
     @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
         container.mBiometricCallback.onAuthenticated()
         waitForIdleSync()
 
-        verify(callback).onDismissed(
+        verify(callback)
+            .onDismissed(
                 eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
                 eq<ByteArray?>(null), /* credentialAttestation */
                 eq(authContainer?.requestId ?: 0L)
-        )
+            )
         assertThat(container.parent).isNull()
     }
 
@@ -309,15 +296,17 @@
         container.mBiometricCallback.onUserCanceled()
         waitForIdleSync()
 
-        verify(callback).onSystemEvent(
+        verify(callback)
+            .onSystemEvent(
                 eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL),
                 eq(authContainer?.requestId ?: 0L)
-        )
-        verify(callback).onDismissed(
+            )
+        verify(callback)
+            .onDismissed(
                 eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
                 eq<ByteArray?>(null), /* credentialAttestation */
                 eq(authContainer?.requestId ?: 0L)
-        )
+            )
         assertThat(container.parent).isNull()
     }
 
@@ -327,19 +316,21 @@
         container.mBiometricCallback.onButtonNegative()
         waitForIdleSync()
 
-        verify(callback).onDismissed(
+        verify(callback)
+            .onDismissed(
                 eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
                 eq<ByteArray?>(null), /* credentialAttestation */
                 eq(authContainer?.requestId ?: 0L)
-        )
+            )
         assertThat(container.parent).isNull()
     }
 
     @Test
     fun testActionTryAgain_sendsTryAgain() {
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
-        )
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
+            )
         container.mBiometricCallback.onButtonTryAgain()
         waitForIdleSync()
 
@@ -352,21 +343,24 @@
         container.mBiometricCallback.onError()
         waitForIdleSync()
 
-        verify(callback).onDismissed(
+        verify(callback)
+            .onDismissed(
                 eq(AuthDialogCallback.DISMISSED_ERROR),
                 eq<ByteArray?>(null), /* credentialAttestation */
                 eq(authContainer?.requestId ?: 0L)
-        )
+            )
         assertThat(authContainer!!.parent).isNull()
     }
 
     @Ignore("b/279650412")
     @Test
     fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
-                    BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
+        val container =
+            initializeFingerprintContainer(
+                authenticators =
+                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
         container.mBiometricCallback.onUseDeviceCredential()
         waitForIdleSync()
 
@@ -376,10 +370,12 @@
 
     @Test
     fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
-                    BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
+        val container =
+            initializeFingerprintContainer(
+                authenticators =
+                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
         container.animateToCredentialUI(false)
         waitForIdleSync()
 
@@ -395,10 +391,12 @@
 
     @Test
     fun testAnimateToCredentialUI_rotateCredentialUI() {
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
-                    BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
+        val container =
+            initializeFingerprintContainer(
+                authenticators =
+                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
         container.animateToCredentialUI(false)
         waitForIdleSync()
 
@@ -437,13 +435,12 @@
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         var isButtonClicked = false
         val contentView =
-                PromptContentViewWithMoreOptionsButton.Builder()
-                        .setMoreOptionsButtonListener(
-                                fakeExecutor) { _, _ -> isButtonClicked = true }
-                        .build()
+            PromptContentViewWithMoreOptionsButton.Builder()
+                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> isButtonClicked = true }
+                .build()
 
         val container =
-                initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView)
+            initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView)
 
         waitForIdleSync()
 
@@ -461,9 +458,9 @@
     @Test
     fun testShowCredentialUI_withDescription() {
         val container =
-                initializeFingerprintContainer(
-                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-                )
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
         waitForIdleSync()
 
         assertThat(container.hasCredentialView()).isTrue()
@@ -475,10 +472,10 @@
         mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val container =
-                initializeFingerprintContainer(
-                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
-                        verticalListContentView = PromptVerticalListContentView.Builder().build()
-                )
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                verticalListContentView = PromptVerticalListContentView.Builder().build()
+            )
         // Two-step credential view should show -
         // 1. biometric prompt without sensor 2. credential view ui
         waitForIdleSync()
@@ -497,14 +494,14 @@
         mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val contentView =
-                PromptContentViewWithMoreOptionsButton.Builder()
-                        .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
-                        .build()
+            PromptContentViewWithMoreOptionsButton.Builder()
+                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
+                .build()
         val container =
-                initializeFingerprintContainer(
-                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
-                        contentViewWithMoreOptionsButton = contentView
-                )
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                contentViewWithMoreOptionsButton = contentView
+            )
         waitForIdleSync()
 
         assertThat(container.hasCredentialView()).isTrue()
@@ -514,13 +511,13 @@
     @Test
     fun testCredentialViewUsesEffectiveUserId() {
         whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn(
-            DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
-        )
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200)))
+            .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
 
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
         waitForIdleSync()
 
         assertThat(container.hasCredentialPatternView()).isTrue()
@@ -531,9 +528,8 @@
     fun testCredentialUI_disablesClickingOnBackground() {
         val container = initializeCredentialPasswordContainer()
         assertThat(container.hasBiometricPrompt()).isFalse()
-        assertThat(
-            container.findViewById<View>(R.id.background)?.isImportantForAccessibility
-        ).isFalse()
+        assertThat(container.findViewById<View>(R.id.background)?.isImportantForAccessibility)
+            .isFalse()
 
         container.findViewById<View>(R.id.background)?.performClick()
         waitForIdleSync()
@@ -552,7 +548,7 @@
     fun testLayoutParams_hasShowWhenLockedFlag() {
         val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
         assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
-                .isTrue()
+            .isTrue()
     }
 
     @Test
@@ -590,20 +586,20 @@
     }
 
     private fun initializeCredentialPasswordContainer(
-            addToView: Boolean = true,
+        addToView: Boolean = true,
     ): TestAuthContainerView {
         whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
-            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-        )
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20)))
+            .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)
 
         // In the credential view, clicking on the background (to cancel authentication) is not
         // valid. Thus, the listener should be null, and it should not be in the accessibility
         // hierarchy.
-        val container = initializeFingerprintContainer(
+        val container =
+            initializeFingerprintContainer(
                 authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
                 addToView = addToView,
-        )
+            )
         waitForIdleSync()
 
         assertThat(container.hasCredentialPasswordView()).isTrue()
@@ -615,26 +611,28 @@
         addToView: Boolean = true,
         verticalListContentView: PromptVerticalListContentView? = null,
         contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
-    ) = initializeContainer(
-        TestAuthContainerView(
-            authenticators = authenticators,
-            fingerprintProps = fingerprintSensorPropertiesInternal(),
+    ) =
+        initializeContainer(
+            TestAuthContainerView(
+                authenticators = authenticators,
+                fingerprintProps = fingerprintSensorPropertiesInternal(),
                 verticalListContentView = verticalListContentView,
-        ),
-        addToView
-    )
+            ),
+            addToView
+        )
 
     private fun initializeCoexContainer(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
         addToView: Boolean = true
-    ) = initializeContainer(
-        TestAuthContainerView(
-            authenticators = authenticators,
-            fingerprintProps = fingerprintSensorPropertiesInternal(),
-            faceProps = faceSensorPropertiesInternal()
-        ),
-        addToView
-    )
+    ) =
+        initializeContainer(
+            TestAuthContainerView(
+                authenticators = authenticators,
+                fingerprintProps = fingerprintSensorPropertiesInternal(),
+                faceProps = faceSensorPropertiesInternal()
+            ),
+            addToView
+        )
 
     private fun initializeContainer(
         view: TestAuthContainerView,
@@ -655,47 +653,50 @@
         faceProps: List<FaceSensorPropertiesInternal> = listOf(),
         verticalListContentView: PromptVerticalListContentView? = null,
         contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
-    ) : AuthContainerView(
-        Config().apply {
-            mContext = this@AuthContainerViewTest.context
-            mCallback = callback
-            mSensorIds = (fingerprintProps.map { it.sensorId } +
-                faceProps.map { it.sensorId }).toIntArray()
-            mSkipAnimation = true
-            mPromptInfo = PromptInfo().apply {
-                this.authenticators = authenticators
-                if (verticalListContentView != null) {
-                    this.contentView = verticalListContentView
-                } else if (contentViewWithMoreOptionsButton != null) {
-                    this.contentView = contentViewWithMoreOptionsButton
-                }
-            }
-            mOpPackageName = OP_PACKAGE_NAME
-        },
-        testScope.backgroundScope,
-        fingerprintProps,
-        faceProps,
-        wakefulnessLifecycle,
-        panelInteractionDetector,
-        userManager,
-        lockPatternUtils,
-        interactionJankMonitor,
-        { promptSelectorInteractor },
-        PromptViewModel(
-            displayStateInteractor,
-            promptSelectorInteractor,
-            context,
-            udfpsOverlayInteractor,
-            biometricStatusInteractor,
-            udfpsUtils,
-            iconProvider,
-            activityTaskManager
-        ),
-        { credentialViewModel },
-        Handler(TestableLooper.get(this).looper),
-        fakeExecutor,
-        vibrator
-    ) {
+    ) :
+        AuthContainerView(
+            Config().apply {
+                mContext = this@AuthContainerViewTest.context
+                mCallback = callback
+                mSensorIds =
+                    (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId })
+                        .toIntArray()
+                mSkipAnimation = true
+                mPromptInfo =
+                    PromptInfo().apply {
+                        this.authenticators = authenticators
+                        if (verticalListContentView != null) {
+                            this.contentView = verticalListContentView
+                        } else if (contentViewWithMoreOptionsButton != null) {
+                            this.contentView = contentViewWithMoreOptionsButton
+                        }
+                    }
+                mOpPackageName = OP_PACKAGE_NAME
+            },
+            testScope.backgroundScope,
+            fingerprintProps,
+            faceProps,
+            wakefulnessLifecycle,
+            panelInteractionDetector,
+            userManager,
+            lockPatternUtils,
+            interactionJankMonitor,
+            { promptSelectorInteractor },
+            PromptViewModel(
+                displayStateInteractor,
+                promptSelectorInteractor,
+                context,
+                udfpsOverlayInteractor,
+                biometricStatusInteractor,
+                udfpsUtils,
+                iconProvider,
+                activityTaskManager
+            ),
+            { credentialViewModel },
+            Handler(TestableLooper.get(this).looper),
+            fakeExecutor,
+            vibrator
+        ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
         }
@@ -717,8 +718,10 @@
         val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
         val lpFlags = layoutParams.flags
 
-        assertThat((lpFlags and WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)
-                != 0).isTrue()
+        assertThat(
+                (lpFlags and WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) != 0
+            )
+            .isTrue()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 3d63c5b..13f2c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintManager
 import android.view.MotionEvent
 import android.view.Surface
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -51,6 +52,7 @@
 
     private lateinit var testScope: TestScope
 
+    @Mock private lateinit var fingerprintManager: FingerprintManager
     @Mock private lateinit var authController: AuthController
     @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
 
@@ -111,6 +113,7 @@
                 context,
                 authController,
                 selectedUserInteractor,
+                fingerprintManager,
                 testScope.backgroundScope
             )
         testScope.runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
deleted file mode 100644
index b986c52..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ /dev/null
@@ -1,129 +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.systemui.keyboard.shortcut.data.repository
-
-import android.view.KeyEvent
-import android.view.KeyboardShortcutGroup
-import android.view.KeyboardShortcutInfo
-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.keyboard.shortcut.shared.model.Shortcut
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
-import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
-import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
-import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
-    @OptIn(ExperimentalCoroutinesApi::class)
-    private val kosmos = testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
-    private val repo = kosmos.shortcutHelperCategoriesRepository
-    private val helper = kosmos.shortcutHelperTestHelper
-    private val testScope = kosmos.testScope
-
-    @Test
-    fun stateActive_imeShortcuts_shortcutInfoCorrectlyConverted() =
-        testScope.runTest {
-            helper.setImeShortcuts(imeShortcutsGroupWithPreviousLanguageSwitchShortcut)
-            val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory)
-
-            helper.showFromActivity()
-
-            assertThat(imeShortcutCategory)
-                .isEqualTo(expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut)
-        }
-
-    @Test
-    fun stateActive_imeShortcuts_onlyUnsupportedShortcuts_discardsAll() =
-        testScope.runTest {
-            helper.setImeShortcuts(imeShortcutsGroupWithUnsupportedShortcutModifiers)
-            val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory)
-
-            helper.showFromActivity()
-
-            assertThat(imeShortcutCategory).isEqualTo(null)
-        }
-
-    private val switchToPreviousLanguageCommand =
-        ShortcutCommand(
-            listOf(KeyEvent.META_CTRL_ON, KeyEvent.META_SHIFT_ON, KeyEvent.KEYCODE_SPACE)
-        )
-
-    private val expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts =
-        shortcutCategory(ShortcutCategoryType.IME) { subCategory("input", emptyList()) }
-
-    private val switchToPreviousLanguageKeyboardShortcutInfo =
-        KeyboardShortcutInfo(
-            /* label = */ "switch to previous language",
-            /* keycode = */ switchToPreviousLanguageCommand.keyCodes[2],
-            /* modifiers = */ switchToPreviousLanguageCommand.keyCodes[0] or
-                switchToPreviousLanguageCommand.keyCodes[1],
-        )
-
-    private val expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut =
-        shortcutCategory(ShortcutCategoryType.IME) {
-            subCategory(
-                "input",
-                listOf(
-                    Shortcut(
-                        switchToPreviousLanguageKeyboardShortcutInfo.label!!.toString(),
-                        listOf(switchToPreviousLanguageCommand)
-                    )
-                )
-            )
-        }
-
-    private val imeShortcutsGroupWithPreviousLanguageSwitchShortcut =
-        listOf(
-            KeyboardShortcutGroup(
-                "input",
-                listOf(
-                    switchToPreviousLanguageKeyboardShortcutInfo,
-                )
-            )
-        )
-
-    private val shortcutInfoWithUnsupportedModifier =
-        KeyboardShortcutInfo(
-            /* label = */ "unsupported shortcut",
-            /* keycode = */ KeyEvent.KEYCODE_SPACE,
-            /* modifiers = */ 32
-        )
-
-    private val imeShortcutsGroupWithUnsupportedShortcutModifiers =
-        listOf(
-            KeyboardShortcutGroup(
-                "input",
-                listOf(
-                    shortcutInfoWithUnsupportedModifier,
-                )
-            )
-        )
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt
index 3caa8f6..0b4e6a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperStateRepositoryTest.kt
@@ -65,17 +65,6 @@
         }
 
     @Test
-    fun state_activeThroughActivity_virtualKeyboardActive_emitsActiveWithVirtualDeviceId() =
-        testScope.runTest {
-            val state by collectLastValue(repo.state)
-
-            fakeInputManager.addVirtualKeyboard()
-            helper.showFromActivity()
-
-            assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD))
-        }
-
-    @Test
     fun state_activeThroughActivity_physicalKeyboardActive_emitsActiveWithDeviceId() =
         testScope.runTest {
             val deviceId = 456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
new file mode 100644
index 0000000..c2814bd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.res.R
+
+object TestShortcuts {
+
+    private val shortcutInfoWithRepeatedLabel =
+        KeyboardShortcutInfo(
+            /* label = */ "Shortcut with repeated label",
+            /* keycode = */ KeyEvent.KEYCODE_H,
+            /* modifiers = */ KeyEvent.META_META_ON,
+        )
+
+    private val shortcutInfoWithRepeatedLabelAlternate =
+        KeyboardShortcutInfo(
+            /* label = */ shortcutInfoWithRepeatedLabel.label,
+            /* keycode = */ KeyEvent.KEYCODE_L,
+            /* modifiers = */ KeyEvent.META_META_ON,
+        )
+
+    private val shortcutInfoWithRepeatedLabelSecondAlternate =
+        KeyboardShortcutInfo(
+            /* label = */ shortcutInfoWithRepeatedLabel.label,
+            /* keycode = */ KeyEvent.KEYCODE_M,
+            /* modifiers = */ KeyEvent.META_SHIFT_ON,
+        )
+
+    private val shortcutWithGroupedRepeatedLabel =
+        shortcut(shortcutInfoWithRepeatedLabel.label!!.toString()) {
+            command {
+                key(R.drawable.ic_ksh_key_meta)
+                key("H")
+            }
+            command {
+                key(R.drawable.ic_ksh_key_meta)
+                key("L")
+            }
+            command {
+                key("Shift")
+                key("M")
+            }
+        }
+
+    private val standardShortcutInfo1 =
+        KeyboardShortcutInfo(
+            /* label = */ "Standard shortcut 1",
+            /* keycode = */ KeyEvent.KEYCODE_N,
+            /* modifiers = */ KeyEvent.META_META_ON,
+        )
+
+    private val standardShortcut1 =
+        shortcut(standardShortcutInfo1.label!!.toString()) {
+            command {
+                key(R.drawable.ic_ksh_key_meta)
+                key("N")
+            }
+        }
+
+    private val standardShortcutInfo2 =
+        KeyboardShortcutInfo(
+            /* label = */ "Standard shortcut 2",
+            /* keycode = */ KeyEvent.KEYCODE_Z,
+            /* modifiers = */ KeyEvent.META_ALT_ON or KeyEvent.META_SHIFT_ON,
+        )
+
+    private val standardShortcut2 =
+        shortcut(standardShortcutInfo2.label!!.toString()) {
+            command {
+                key("Alt")
+                key("Shift")
+                key("Z")
+            }
+        }
+
+    private val standardShortcutInfo3 =
+        KeyboardShortcutInfo(
+            /* label = */ "Standard shortcut 3",
+            /* keycode = */ KeyEvent.KEYCODE_J,
+            /* modifiers = */ KeyEvent.META_CTRL_ON,
+        )
+
+    private val standardShortcut3 =
+        shortcut(standardShortcutInfo3.label!!.toString()) {
+            command {
+                key("Ctrl")
+                key("J")
+            }
+        }
+
+    private val shortcutInfoWithUnsupportedModifiers =
+        KeyboardShortcutInfo(
+            /* label = */ "Shortcut with unsupported modifiers",
+            /* keycode = */ KeyEvent.KEYCODE_A,
+            /* modifiers = */ KeyEvent.META_META_ON or KeyEvent.KEYCODE_SPACE,
+        )
+
+    private val groupWithRepeatedShortcutLabels =
+        KeyboardShortcutGroup(
+            "Group with duplicate labels",
+            listOf(
+                shortcutInfoWithRepeatedLabel,
+                shortcutInfoWithRepeatedLabelAlternate,
+                shortcutInfoWithRepeatedLabelSecondAlternate
+            )
+        )
+
+    private val subCategoryWithGroupedRepeatedShortcutLabels =
+        ShortcutSubCategory(
+            label = groupWithRepeatedShortcutLabels.label!!.toString(),
+            shortcuts = listOf(shortcutWithGroupedRepeatedLabel)
+        )
+
+    private val groupWithStandardShortcutInfo =
+        KeyboardShortcutGroup("Standard group", listOf(standardShortcutInfo1))
+
+    private val subCategoryWithStandardShortcut =
+        ShortcutSubCategory(
+            label = groupWithStandardShortcutInfo.label!!.toString(),
+            shortcuts = listOf(standardShortcut1)
+        )
+
+    private val groupWithOnlyUnsupportedModifierShortcut =
+        KeyboardShortcutGroup(
+            "Group with unsupported modifiers",
+            listOf(shortcutInfoWithUnsupportedModifiers)
+        )
+
+    private val groupWithSupportedAndUnsupportedModifierShortcut =
+        KeyboardShortcutGroup(
+            "Group with mix of supported and not supported modifiers",
+            listOf(standardShortcutInfo3, shortcutInfoWithUnsupportedModifiers)
+        )
+
+    private val subCategoryWithUnsupportedShortcutsRemoved =
+        ShortcutSubCategory(
+            groupWithSupportedAndUnsupportedModifierShortcut.label!!.toString(),
+            listOf(standardShortcut3)
+        )
+
+    private val standardGroup1 =
+        KeyboardShortcutGroup(
+            "Standard group 1",
+            listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+        )
+
+    private val standardSubCategory1 =
+        ShortcutSubCategory(
+            standardGroup1.label!!.toString(),
+            listOf(standardShortcut1, standardShortcut2, standardShortcut3)
+        )
+
+    private val standardGroup2 =
+        KeyboardShortcutGroup(
+            "Standard group 2",
+            listOf(standardShortcutInfo3, standardShortcutInfo2, standardShortcutInfo1)
+        )
+
+    private val standardSubCategory2 =
+        ShortcutSubCategory(
+            standardGroup2.label!!.toString(),
+            listOf(standardShortcut3, standardShortcut2, standardShortcut1)
+        )
+    private val standardGroup3 =
+        KeyboardShortcutGroup(
+            "Standard group 3",
+            listOf(standardShortcutInfo2, standardShortcutInfo1)
+        )
+
+    private val standardSubCategory3 =
+        ShortcutSubCategory(
+            standardGroup3.label!!.toString(),
+            listOf(standardShortcut2, standardShortcut1)
+        )
+    val imeGroups = listOf(standardGroup1, standardGroup2, standardGroup3)
+    val imeCategory =
+        ShortcutCategory(
+            type = ShortcutCategoryType.IME,
+            subCategories = listOf(standardSubCategory1, standardSubCategory2, standardSubCategory3)
+        )
+
+    val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
+    val systemCategory =
+        ShortcutCategory(
+            type = ShortcutCategoryType.SYSTEM,
+            subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1)
+        )
+
+    val multitaskingGroups = listOf(standardGroup2, standardGroup1)
+    val multitaskingCategory =
+        ShortcutCategory(
+            type = ShortcutCategoryType.MULTI_TASKING,
+            subCategories = listOf(standardSubCategory2, standardSubCategory1)
+        )
+
+    val groupsWithDuplicateShortcutLabels =
+        listOf(groupWithRepeatedShortcutLabels, groupWithStandardShortcutInfo)
+
+    val subCategoriesWithGroupedDuplicatedShortcutLabels =
+        listOf(subCategoryWithGroupedRepeatedShortcutLabels, subCategoryWithStandardShortcut)
+
+    val groupsWithUnsupportedModifier =
+        listOf(
+            groupWithStandardShortcutInfo,
+            groupWithOnlyUnsupportedModifierShortcut,
+            groupWithSupportedAndUnsupportedModifierShortcut
+        )
+
+    val subCategoriesWithUnsupportedModifiersRemoved =
+        listOf(subCategoryWithStandardShortcut, subCategoryWithUnsupportedShortcutsRemoved)
+
+    val groupsWithOnlyUnsupportedModifiers = listOf(groupWithOnlyUnsupportedModifierShortcut)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index c00e7e79..c6a7565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -16,18 +16,16 @@
 
 package com.android.systemui.keyboard.shortcut.domain.interactor
 
-import android.view.KeyEvent
-import android.view.KeyboardShortcutGroup
-import android.view.KeyboardShortcutInfo
 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.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
-import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
 import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
 import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
@@ -56,15 +54,16 @@
             it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
             it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
         }
+
     private val testScope = kosmos.testScope
     private val interactor = kosmos.shortcutHelperCategoriesInteractor
     private val helper = kosmos.shortcutHelperTestHelper
 
     @Before
-    fun setUp() {
-        // Setting these sources as empty temporarily. Will be populated in follow up CL.
-        systemShortcutsSource.setGroups(emptyList())
-        multitaskingShortcutsSource.setGroups(emptyList())
+    fun setShortcuts() {
+        helper.setImeShortcuts(TestShortcuts.imeGroups)
+        systemShortcutsSource.setGroups(TestShortcuts.systemGroups)
+        multitaskingShortcutsSource.setGroups(TestShortcuts.multitaskingGroups)
     }
 
     @Test
@@ -78,12 +77,17 @@
     @Test
     fun categories_stateActive_emitsAllCategoriesInOrder() =
         testScope.runTest {
-            helper.setImeShortcuts(imeShortcutGroups)
             val categories by collectLastValue(interactor.shortcutCategories)
 
             helper.showFromActivity()
 
-            assertThat(categories).containsExactly(imeShortcutCategory).inOrder()
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    TestShortcuts.multitaskingCategory,
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
         }
 
     @Test
@@ -96,159 +100,177 @@
             assertThat(categories).isEmpty()
         }
 
-    fun categories_stateActive_emitsGroupedShortcuts() =
+    @Test
+    fun categories_stateActive_imeShortcutsWithDuplicateLabels_emitsGroupedShortcuts() =
         testScope.runTest {
-            helper.setImeShortcuts(imeShortcutsGroupsWithDuplicateLabels)
+            helper.setImeShortcuts(TestShortcuts.groupsWithDuplicateShortcutLabels)
+
             val categories by collectLastValue(interactor.shortcutCategories)
 
             helper.showFromActivity()
 
-            assertThat(categories).containsExactly(expectedGroupedShortcutCategories)
-        }
-
-    private val switchToNextLanguageShortcut =
-        shortcut(label = "switch to next language") {
-            command(KeyEvent.META_CTRL_ON, KeyEvent.KEYCODE_SPACE)
-        }
-
-    private val switchToNextLanguageKeyboardShortcutInfo =
-        KeyboardShortcutInfo(
-            /* label = */ switchToNextLanguageShortcut.label,
-            /* keycode = */ switchToNextLanguageShortcut.commands[0].keyCodes[1],
-            /* modifiers = */ switchToNextLanguageShortcut.commands[0].keyCodes[0],
-        )
-
-    private val switchToNextLanguageShortcutAlternative =
-        shortcut("switch to next language") {
-            command(KeyEvent.META_CTRL_ON, KeyEvent.KEYCODE_SPACE)
-        }
-
-    private val switchToNextLanguageKeyboardShortcutInfoAlternative =
-        KeyboardShortcutInfo(
-            /* label = */ switchToNextLanguageShortcutAlternative.label,
-            /* keycode = */ switchToNextLanguageShortcutAlternative.commands[0].keyCodes[1],
-            /* modifiers = */ switchToNextLanguageShortcutAlternative.commands[0].keyCodes[0],
-        )
-
-    private val switchToPreviousLanguageShortcut =
-        shortcut("switch to previous language") {
-            command(
-                KeyEvent.META_SHIFT_ON,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        }
-
-    private val switchToPreviousLanguageKeyboardShortcutInfo =
-        KeyboardShortcutInfo(
-            /* label = */ switchToPreviousLanguageShortcut.label,
-            /* keycode = */ switchToPreviousLanguageShortcut.commands[0].keyCodes[1],
-            /* modifiers = */ switchToPreviousLanguageShortcut.commands[0].keyCodes[0],
-        )
-
-    private val switchToPreviousLanguageShortcutAlternative =
-        shortcut("switch to previous language") {
-            command(
-                KeyEvent.META_SHIFT_ON,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        }
-
-    private val switchToPreviousLanguageKeyboardShortcutInfoAlternative =
-        KeyboardShortcutInfo(
-            /* label = */ switchToPreviousLanguageShortcutAlternative.label,
-            /* keycode = */ switchToPreviousLanguageShortcutAlternative.commands[0].keyCodes[1],
-            /* modifiers = */ switchToPreviousLanguageShortcutAlternative.commands[0].keyCodes[0],
-        )
-
-    private val showOnscreenKeyboardShortcut =
-        shortcut(label = "Show on-screen keyboard") {
-            command(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_K)
-        }
-
-    private val showOnScreenKeyboardShortcutInfo =
-        KeyboardShortcutInfo(
-            /* label = */ showOnscreenKeyboardShortcut.label,
-            /* keycode = */ showOnscreenKeyboardShortcut.commands[0].keyCodes[1],
-            /* modifiers = */ showOnscreenKeyboardShortcut.commands[0].keyCodes[0],
-        )
-
-    private val accessClipboardShortcut =
-        shortcut(label = "Access clipboard") { command(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_V) }
-
-    private val accessClipboardShortcutInfo =
-        KeyboardShortcutInfo(
-            /* label = */ accessClipboardShortcut.label,
-            /* keycode = */ accessClipboardShortcut.commands[0].keyCodes[1],
-            /* modifiers = */ accessClipboardShortcut.commands[0].keyCodes[0],
-        )
-
-    private val imeShortcutGroups =
-        listOf(
-            KeyboardShortcutGroup(
-                /* label = */ "input",
-                /* shortcutInfoList = */ listOf(
-                    switchToNextLanguageKeyboardShortcutInfo,
-                    switchToPreviousLanguageKeyboardShortcutInfo
-                )
-            )
-        )
-
-    private val imeShortcutCategory =
-        ShortcutCategory(
-            type = ShortcutCategoryType.IME,
-            subCategories =
-                listOf(
-                    ShortcutSubCategory(
-                        imeShortcutGroups[0].label.toString(),
-                        listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut)
-                    )
-                )
-        )
-
-    private val imeShortcutsGroupsWithDuplicateLabels =
-        listOf(
-            KeyboardShortcutGroup(
-                "input",
-                listOf(
-                    switchToNextLanguageKeyboardShortcutInfo,
-                    switchToNextLanguageKeyboardShortcutInfoAlternative,
-                    switchToPreviousLanguageKeyboardShortcutInfo,
-                    switchToPreviousLanguageKeyboardShortcutInfoAlternative
-                )
-            ),
-            KeyboardShortcutGroup(
-                "Gboard",
-                listOf(
-                    showOnScreenKeyboardShortcutInfo,
-                    accessClipboardShortcutInfo,
-                )
-            )
-        )
-
-    private val expectedGroupedShortcutCategories =
-        ShortcutCategory(
-            type = ShortcutCategoryType.IME,
-            subCategories =
-                listOf(
-                    ShortcutSubCategory(
-                        imeShortcutsGroupsWithDuplicateLabels[0].label.toString(),
-                        listOf(
-                            switchToNextLanguageShortcut.copy(
-                                commands =
-                                    switchToNextLanguageShortcut.commands +
-                                        switchToNextLanguageShortcutAlternative.commands
-                            ),
-                            switchToPreviousLanguageShortcut.copy(
-                                commands =
-                                    switchToPreviousLanguageShortcut.commands +
-                                        switchToPreviousLanguageShortcutAlternative.commands
-                            )
-                        ),
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    TestShortcuts.multitaskingCategory,
+                    ShortcutCategory(
+                        type = IME,
+                        subCategories =
+                            TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
                     ),
-                    ShortcutSubCategory(
-                        imeShortcutsGroupsWithDuplicateLabels[1].label.toString(),
-                        listOf(showOnscreenKeyboardShortcut, accessClipboardShortcut),
-                    )
                 )
-        )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_systemShortcutsWithDuplicateLabels_emitsGroupedShortcuts() =
+        testScope.runTest {
+            systemShortcutsSource.setGroups(TestShortcuts.groupsWithDuplicateShortcutLabels)
+
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    ShortcutCategory(
+                        type = SYSTEM,
+                        subCategories =
+                            TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
+                    ),
+                    TestShortcuts.multitaskingCategory,
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_multiTaskingShortcutsWithDuplicateLabels_emitsGroupedShortcuts() =
+        testScope.runTest {
+            multitaskingShortcutsSource.setGroups(TestShortcuts.groupsWithDuplicateShortcutLabels)
+
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    ShortcutCategory(
+                        type = MULTI_TASKING,
+                        subCategories =
+                            TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
+                    ),
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_imeShortcutsWithUnsupportedModifiers_discardUnsupported() =
+        testScope.runTest {
+            helper.setImeShortcuts(TestShortcuts.groupsWithUnsupportedModifier)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    TestShortcuts.multitaskingCategory,
+                    ShortcutCategory(
+                        type = IME,
+                        subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
+                    ),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_systemShortcutsWithUnsupportedModifiers_discardUnsupported() =
+        testScope.runTest {
+            systemShortcutsSource.setGroups(TestShortcuts.groupsWithUnsupportedModifier)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    ShortcutCategory(
+                        type = SYSTEM,
+                        subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
+                    ),
+                    TestShortcuts.multitaskingCategory,
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_multitaskingShortcutsWithUnsupportedModifiers_discardUnsupported() =
+        testScope.runTest {
+            multitaskingShortcutsSource.setGroups(TestShortcuts.groupsWithUnsupportedModifier)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    ShortcutCategory(
+                        type = MULTI_TASKING,
+                        subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
+                    ),
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_imeShortcutsWitOnlyUnsupportedModifiers_discardsCategory() =
+        testScope.runTest {
+            helper.setImeShortcuts(TestShortcuts.groupsWithOnlyUnsupportedModifiers)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    TestShortcuts.multitaskingCategory,
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_systemShortcutsWitOnlyUnsupportedModifiers_discardsCategory() =
+        testScope.runTest {
+            systemShortcutsSource.setGroups(TestShortcuts.groupsWithOnlyUnsupportedModifiers)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.multitaskingCategory,
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun categories_stateActive_multitaskingShortcutsWitOnlyUnsupportedModifiers_discardsCategory() =
+        testScope.runTest {
+            multitaskingShortcutsSource.setGroups(TestShortcuts.groupsWithOnlyUnsupportedModifiers)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            assertThat(categories)
+                .containsExactly(
+                    TestShortcuts.systemCategory,
+                    TestShortcuts.imeCategory,
+                )
+                .inOrder()
+        }
 }
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 f32e775..c9871f1 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
@@ -21,6 +21,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
@@ -28,12 +31,18 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.mockTopActivityClassName
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.shared.system.activityManagerWrapper
 import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.assertValuesMatch
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,16 +53,22 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val underTest = kosmos.keyguardSurfaceBehindInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val inWindowUnlockInteractor = kosmos.inWindowLauncherUnlockAnimationInteractor
-    private val activityManagerWrapper = kosmos.activityManagerWrapper
+    private lateinit var testScope: TestScope
+    private lateinit var underTest: KeyguardSurfaceBehindInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var inWindowUnlockInteractor: InWindowLauncherUnlockAnimationInteractor
+    private lateinit var activityManagerWrapper: ActivityManagerWrapper
 
     private val LAUNCHER_ACTIVITY_NAME = "launcher"
 
     @Before
     fun setUp() {
+        testScope = kosmos.testScope
+        underTest = kosmos.keyguardSurfaceBehindInteractor
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        inWindowUnlockInteractor = kosmos.inWindowLauncherUnlockAnimationInteractor
+        activityManagerWrapper = kosmos.activityManagerWrapper
+
         inWindowUnlockInteractor.setLauncherActivityClass(LAUNCHER_ACTIVITY_NAME)
 
         // Default to having something other than Launcher on top.
@@ -61,6 +76,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun testSurfaceBehindModel_toAppSurface() =
         testScope.runTest {
             val values by collectValues(underTest.viewParams)
@@ -136,6 +152,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun testSurfaceBehindModel_toLauncher() =
         testScope.runTest {
             val values by collectValues(underTest.viewParams)
@@ -196,6 +213,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun testSurfaceBehindModel_fromNotificationLaunch() =
         testScope.runTest {
             val values by collectValues(underTest.viewParams)
@@ -230,6 +248,105 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun testSurfaceBehindModel_toAppSurface_scene_container() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    // We're initialized in LOCKSCREEN.
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                )
+
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+
+            values.assertValuesMatch(
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+                // Once we start a transition to GONE, we should fade in and translate up. The exact
+                // start value depends on screen density, so just look for != 0.
+                {
+                    it.animateFromAlpha == 0f &&
+                        it.alpha == 1f &&
+                        it.animateFromTranslationY != 0f &&
+                        it.translationY == 0f
+                }
+            )
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            values.assertValuesMatch(
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+                {
+                    it.animateFromAlpha == 0f &&
+                        it.alpha == 1f &&
+                        it.animateFromTranslationY != 0f &&
+                        it.translationY == 0f
+                },
+                // Once the current state is GONE, we should default to alpha = 1f.
+                { it == KeyguardSurfaceBehindModel(alpha = 1f) }
+            )
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testSurfaceBehindModel_toLauncher_scene_container() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+            activityManagerWrapper.mockTopActivityClassName(LAUNCHER_ACTIVITY_NAME)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    // We're initialized in LOCKSCREEN.
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                )
+                .inOrder()
+
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+
+            assertThat(values)
+                .containsExactly(
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                    // We should instantly set alpha = 1, with no animations, when Launcher is
+                    // behind
+                    // the keyguard since we're playing in-window animations.
+                    KeyguardSurfaceBehindModel(alpha = 1f),
+                )
+                .inOrder()
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            assertThat(values)
+                .containsExactly(
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                    // Should have remained at alpha = 1f through the entire animation.
+                    KeyguardSurfaceBehindModel(alpha = 1f),
+                )
+                .inOrder()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testSurfaceBehindModel_fromNotificationLaunch_scene_container() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+            runCurrent()
+
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+
+            values.assertValuesMatch(
+                // We should be at alpha = 0f during the animation.
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+            )
+        }
+
+    @Test
+    @DisableSceneContainer
     fun notificationLaunchFromLockscreen_isAnimatingSurfaceTrue() =
         testScope.runTest {
             val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
@@ -253,6 +370,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun notificationLaunchFromGone_isAnimatingSurfaceFalse() =
         testScope.runTest {
             val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
@@ -276,6 +394,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun notificationLaunchFalse_isAnimatingSurfaceFalse() =
         testScope.runTest {
             val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
@@ -297,4 +416,44 @@
             runCurrent()
             assertThat(isAnimatingSurface).isFalse()
         }
+
+    @Test
+    @EnableSceneContainer
+    fun notificationLaunchFromLockscreen_isAnimatingSurfaceTrue_scene_container() =
+        testScope.runTest {
+            val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+
+            assertThat(isAnimatingSurface).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun notificationLaunchFromGone_isAnimatingSurfaceFalse_scene_container() =
+        testScope.runTest {
+            val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+            assertThat(isAnimatingSurface).isFalse()
+
+            kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+            assertThat(isAnimatingSurface).isFalse()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun notificationLaunchFalse_isAnimatingSurfaceFalse_scene_container() =
+        testScope.runTest {
+            val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
+            runCurrent()
+            assertThat(isAnimatingSurface).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 246cfbf..824132a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
@@ -124,7 +123,6 @@
 
     private val powerInteractor by lazy { kosmos.powerInteractor }
     private val communalInteractor by lazy { kosmos.communalInteractor }
-    private val dockManager by lazy { kosmos.fakeDockManager }
 
     companion object {
         @JvmStatic
@@ -583,6 +581,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dozingToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -597,8 +596,8 @@
 
             assertThat(transitionRepository)
                 .startedTransition(
-                    to = KeyguardState.PRIMARY_BOUNCER,
                     from = KeyguardState.DOZING,
+                    to = KeyguardState.PRIMARY_BOUNCER,
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -633,6 +632,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dozingToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -654,8 +654,8 @@
 
             assertThat(transitionRepository)
                 .startedTransition(
-                    to = KeyguardState.GLANCEABLE_HUB,
                     from = KeyguardState.DOZING,
+                    to = KeyguardState.GLANCEABLE_HUB,
                     animatorAssertion = { it.isNotNull() }
                 )
 
@@ -1452,6 +1452,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index f624f20..b7fb759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -57,7 +57,9 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyString
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
 
@@ -75,6 +77,9 @@
 
     private lateinit var mockitoSession: MockitoSession
 
+    private val spiedContext = spy(context)
+    private val spiedResources = spy(spiedContext.resources)
+
     @Before
     fun setUp() {
         mockitoSession =
@@ -100,6 +105,8 @@
                 )
             )
             .thenReturn(listOf("com.google.test.notes"))
+
+        `when`(spiedContext.resources).thenReturn(spiedResources)
     }
 
     @After
@@ -109,7 +116,7 @@
 
     private fun createUnderTest(isEnabled: Boolean = true): KeyguardQuickAffordanceConfig =
         NoteTaskQuickAffordanceConfig(
-            context = context,
+            context = spiedContext,
             controller = controller,
             stylusManager = stylusManager,
             userManager = userManager,
@@ -132,126 +139,262 @@
         )
 
     // region lockScreenState
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUsed_userUnlocked_isSelected_shouldEmitVisible() = runTest {
-        val underTest = createUnderTest()
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(underTest)
-
-        val actual by collectLastValue(underTest.lockScreenState)
-
-        assertThat(actual).isEqualTo(createLockScreenStateVisible())
-    }
-
-    @Test
-    fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() =
+    fun lockScreenState_stylusUnused_userLocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
         runTest {
             val underTest = createUnderTest()
             TestConfig()
-                .setStylusEverUsed(true)
-                .setUserUnlocked(true)
-                .setConfigSelections(underTest)
-            whenever(
-                    roleManager.getRoleHoldersAsUser(
-                        eq(RoleManager.ROLE_NOTES),
-                        any(UserHandle::class.java)
-                    )
-                )
-                .thenReturn(emptyList())
+                .setStylusEverUsed(false)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections()
 
             val actual by collectLastValue(underTest.lockScreenState)
 
             assertThat(actual).isEqualTo(LockScreenState.Hidden)
         }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
-        val underTest = createUnderTest()
-        TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(underTest)
+    fun lockScreenState_stylusUnused_userLocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections(underTest)
 
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUsed_userLocked_isSelected_shouldEmitHidden() = runTest {
-        val underTest = createUnderTest()
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(underTest)
+    fun lockScreenState_stylusUnused_userLocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections()
 
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
+    fun lockScreenState_stylusUnused_userLocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections(underTest)
 
-        val underTest = createUnderTest()
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUnused_userUnlocked_noSelected_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections()
+    fun lockScreenState_stylusUnused_userUnlocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections()
 
-        val underTest = createUnderTest()
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUsed_userLocked_noSelected_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections()
+    fun lockScreenState_stylusUnused_userUnlocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections(underTest)
 
-        val underTest = createUnderTest()
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(mock())
+    fun lockScreenState_stylusUnused_userUnlocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections()
 
-        val underTest = createUnderTest()
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUnused_userUnlocked_customSelections_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(mock())
+    fun lockScreenState_stylusUnused_userUnlocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitVisible() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(false)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections(underTest)
 
-        val underTest = createUnderTest()
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(createLockScreenStateVisible())
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_stylusUsed_userLocked_customSelections_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(mock())
+    fun lockScreenState_stylusUsed_userLocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections()
 
-        val underTest = createUnderTest()
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
 
+    @Suppress("ktlint:standard:max-line-length")
     @Test
-    fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest {
-        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
+    fun lockScreenState_stylusUsed_userLocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections(underTest)
 
-        val underTest = createUnderTest(isEnabled = false)
-        val actual by collectLastValue(underTest.lockScreenState)
+            val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(LockScreenState.Hidden)
-    }
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
+
+    @Suppress("ktlint:standard:max-line-length")
+    @Test
+    fun lockScreenState_stylusUsed_userLocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections()
+
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
+
+    @Suppress("ktlint:standard:max-line-length")
+    @Test
+    fun lockScreenState_stylusUsed_userLocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(false)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections(underTest)
+
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
+
+    @Suppress("ktlint:standard:max-line-length")
+    @Test
+    fun lockScreenState_stylusUsed_userUnlocked_customizationDisabled_notesLockScreenShortcutNotSelected_shouldEmitVisible() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections()
+
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(createLockScreenStateVisible())
+        }
+
+    @Suppress("ktlint:standard:max-line-length")
+    @Test
+    fun lockScreenState_stylusUsed_userUnlocked_customizationDisabled_notesLockScreenShortcutSelected_shouldEmitVisible() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(false)
+                .setConfigSelections(underTest)
+
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(createLockScreenStateVisible())
+        }
+
+    @Suppress("ktlint:standard:max-line-length")
+    @Test
+    fun lockScreenState_stylusUsed_userUnlocked_customizationEnabled_notesLockScreenShortcutNotSelected_shouldEmitHidden() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections()
+
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
+
+    @Suppress("ktlint:standard:max-line-length")
+    @Test
+    fun lockScreenState_stylusUsed_userUnlocked_customizationEnabled_notesLockScreenShortcutSelected_shouldEmitVisible() =
+        runTest {
+            val underTest = createUnderTest()
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(true)
+                .setLockScreenCustomizationEnabled(true)
+                .setConfigSelections(underTest)
+
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(createLockScreenStateVisible())
+        }
+
     // endregion
 
     @Test
@@ -294,18 +437,24 @@
             .isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)
         assertThat(disabled.actionIntent?.`package`).isEqualTo(context.packageName)
     }
+
     // endregion
 
     private inner class TestConfig {
 
         fun setStylusEverUsed(value: Boolean) = also {
-            whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(value)
+            whenever(InputSettings.isStylusEverUsed(spiedContext)).thenReturn(value)
         }
 
         fun setUserUnlocked(value: Boolean) = also {
             whenever(userManager.isUserUnlocked).thenReturn(value)
         }
 
+        fun setLockScreenCustomizationEnabled(value: Boolean) = also {
+            `when`(spiedResources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled))
+                .thenReturn(value)
+        }
+
         fun setConfigSelections(vararg values: KeyguardQuickAffordanceConfig) = also {
             val slotKey = "bottom-right"
             val configSnapshots = values.toList()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index e7ca091..b80d1a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -419,7 +419,7 @@
         mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
                 new ShadeAnimationRepository(), mShadeRepository);
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
-        when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
+        when(mKeyguardTransitionInteractor.isInTransitionWhere(any(), any())).thenReturn(
                 MutableStateFlow(false));
         when(mKeyguardTransitionInteractor.isInTransition(any(), any()))
                 .thenReturn(emptyFlow());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 5c45b2e..3669e3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -64,6 +64,10 @@
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
+    @Mock private SectionHeaderController mNewsHeaderController;
+    @Mock private SectionHeaderController mSocialHeaderController;
+    @Mock private SectionHeaderController mRecsHeaderController;
+    @Mock private SectionHeaderController mPromoHeaderController;
 
     private NotificationSectionsManager mSectionsManager;
 
@@ -94,7 +98,11 @@
                         mIncomingHeaderController,
                         mPeopleHeaderController,
                         mAlertingHeaderController,
-                        mSilentHeaderController
+                        mSilentHeaderController,
+                        mNewsHeaderController,
+                        mSocialHeaderController,
+                        mRecsHeaderController,
+                        mPromoHeaderController
                 );
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 770c424..a925ccf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -49,6 +49,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.DimenRes;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.SystemClock;
@@ -138,7 +139,7 @@
     @Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
     @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private NotificationShelf mNotificationShelf;
-    @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
+    @Mock private NotificationStackSizeCalculator mStackSizeCalculator;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private AvalancheController mAvalancheController;
@@ -197,7 +198,7 @@
         // refer to the CUT's member variables, not the spy's member variables.
         mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null);
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
-                mNotificationStackSizeCalculator);
+                mStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
@@ -233,6 +234,32 @@
     }
 
     @Test
+    @EnableSceneContainer
+    public void testIntrinsicStackHeight() {
+        int stackHeight = 300;
+        when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
+                .thenReturn((float) stackHeight);
+
+        mStackScroller.updateContentHeight();
+
+        assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeight);
+    }
+
+    @Test
+    @DisableSceneContainer
+    public void testIntrinsicStackHeight_includesTopScrimPadding() {
+        int stackHeight = 300;
+        int topScrimPadding = px(R.dimen.notification_side_paddings);
+        when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
+                .thenReturn((float) stackHeight);
+
+        mStackScroller.updateContentHeight();
+
+        assertThat(mStackScroller.getIntrinsicStackHeight())
+                .isEqualTo(stackHeight + topScrimPadding);
+    }
+
+    @Test
     @DisableSceneContainer // TODO(b/312473478): address disabled test
     public void testUpdateStackHeight_qsExpansionZero() {
         final float expansionFraction = 0.2f;
@@ -819,7 +846,7 @@
     @DisableSceneContainer // TODO(b/312473478): address disabled test
     public void setFractionToShade_recomputesStackHeight() {
         mStackScroller.setFractionToShade(1f);
-        verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
+        verify(mStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
     }
 
     @Test
@@ -1231,6 +1258,10 @@
         assertEquals(expected, mAmbientState.isClearAllInProgress());
     }
 
+    private int px(@DimenRes int id) {
+        return mTestableResources.getResources().getDimensionPixelSize(id);
+    }
+
     private static void mockBoundsOnScreen(View view, Rect bounds) {
         doAnswer(invocation -> {
             Rect out = invocation.getArgument(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index b12c098..c2a7b52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -115,6 +115,17 @@
     }
 
     @Test
+    @EnableSceneContainer
+    fun resetViewStates_childPositionedAtStackTop() {
+        val stackTop = 100f
+        ambientState.stackTop = stackTop
+
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        assertThat(notificationRow.viewState.yTranslation).isEqualTo(stackTop)
+    }
+
+    @Test
     fun resetViewStates_defaultHun_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
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 0cb28cb..68e17c1 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
@@ -316,6 +316,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setBatteryListening_true_callbackAdded() {
         mController.setBatteryListening(true);
 
@@ -323,6 +324,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setBatteryListening_false_callbackRemoved() {
         // First set to true so that we know setting to false is a change in state.
         mController.setBatteryListening(true);
@@ -333,6 +335,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
         mController.setBatteryListening(true);
         mController.setBatteryListening(true);
@@ -372,6 +375,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_alphaAndVisibilityGiven_viewUpdated() {
         // Verify the initial values so we know the method triggers changes.
         assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f);
@@ -386,6 +390,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
         mController.onViewAttached();
         setDisableSystemIcons(true);
@@ -397,6 +402,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_notKeyguardState_nothingUpdated() {
         mController.onViewAttached();
         updateStateToNotKeyguard();
@@ -409,6 +415,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -424,6 +431,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_bypassNotEnabled_viewShown() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -438,6 +446,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_shouldNotListenForFace_viewShown() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -452,6 +461,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_panelExpandedHeightZero_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -464,6 +474,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_dragProgressOne_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -476,6 +487,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_disableSystemInfoFalse_viewShown() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -487,6 +499,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_disableSystemInfoTrue_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -498,6 +511,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_disableSystemIconsFalse_viewShown() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -509,6 +523,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateViewState_disableSystemIconsTrue_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -608,6 +623,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setAlpha_explicitAlpha_setsExplicitAlpha() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -618,6 +634,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -632,6 +649,7 @@
     // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
 
     @Test
+    @DisableSceneContainer
     public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -644,6 +662,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
         mController.onViewAttached();
         updateStateToKeyguard();
@@ -733,6 +752,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index 36ac4a4..c4f93d1 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -18,6 +18,7 @@
 
 import android.view.InputDevice
 import android.view.KeyCharacterMap
+import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import org.mockito.ArgumentMatchers.anyInt
@@ -25,7 +26,18 @@
 
 class FakeInputManager {
 
-    private val devices = mutableMapOf<Int, InputDevice>()
+    private val keyCharacterMap = KeyCharacterMap.load(VIRTUAL_KEYBOARD)
+
+    private val virtualKeyboard =
+        InputDevice.Builder()
+            .setId(VIRTUAL_KEYBOARD)
+            .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+            .setSources(InputDevice.SOURCE_KEYBOARD)
+            .setEnabled(true)
+            .setKeyCharacterMap(keyCharacterMap)
+            .build()
+
+    private val devices = mutableMapOf<Int, InputDevice>(VIRTUAL_KEYBOARD to virtualKeyboard)
 
     val inputManager =
         mock<InputManager> {
@@ -56,10 +68,6 @@
         addKeyboard(id, enabled)
     }
 
-    fun addVirtualKeyboard(enabled: Boolean = true) {
-        addKeyboard(id = KeyCharacterMap.VIRTUAL_KEYBOARD, enabled)
-    }
-
     private fun addKeyboard(id: Int, enabled: Boolean = true) {
         devices[id] =
             InputDevice.Builder()
@@ -67,6 +75,7 @@
                 .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
                 .setSources(InputDevice.SOURCE_KEYBOARD)
                 .setEnabled(enabled)
+                .setKeyCharacterMap(keyCharacterMap)
                 .build()
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 9dae44d..7c53639 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -18,6 +18,7 @@
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
 import android.app.trust.TrustManager
+import android.hardware.fingerprint.FingerprintManager
 import android.os.UserManager
 import android.service.notification.NotificationListenerService
 import android.util.DisplayMetrics
@@ -94,6 +95,7 @@
     @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
     @get:Provides val dozeParameters: DozeParameters = mock(),
     @get:Provides val dumpManager: DumpManager = mock(),
+    @get:Provides val fingerprintManager: FingerprintManager = mock(),
     @get:Provides val headsUpManager: HeadsUpManager = mock(),
     @get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
     @get:Provides val keyguardBypassController: KeyguardBypassController = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
index cbfc768..ae592b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -17,17 +17,20 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.content.applicationContext
+import android.hardware.fingerprint.FingerprintManager
 import com.android.systemui.biometrics.authController
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
 
 val Kosmos.udfpsOverlayInteractor by Fixture {
     UdfpsOverlayInteractor(
         context = applicationContext,
         authController = authController,
         selectedUserInteractor = selectedUserInteractor,
+        fingerprintManager = mock<FingerprintManager>(),
         scope = applicationCoroutineScope,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 385a6dc..dab70f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -59,10 +59,13 @@
 val Kosmos.shortcutHelperCategoriesRepository by
     Kosmos.Fixture {
         ShortcutHelperCategoriesRepository(
+            applicationContext,
+            testDispatcher,
             shortcutHelperSystemShortcutsSource,
             shortcutHelperMultiTaskingShortcutsSource,
             windowManager,
-            shortcutHelperStateRepository
+            fakeInputManager.inputManager,
+            shortcutHelperStateRepository,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
index 78a419f..ce317d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
@@ -32,6 +32,7 @@
         FromAlternateBouncerTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index 42af25e..ae138c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -29,6 +29,7 @@
         FromAodTransitionInteractor(
             transitionRepository = fakeKeyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index edf77a0..e7e007f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -30,6 +30,7 @@
         FromDozingTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
index f7a9d59..7ebef10 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
@@ -28,6 +28,7 @@
         FromDreamingLockscreenHostedTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index 135644c..a9be06d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -28,6 +28,7 @@
         FromDreamingTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
index 1695327..6784658 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -28,6 +28,7 @@
         FromGlanceableHubTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 4039ee6..317294f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -31,6 +31,7 @@
         FromGoneTransitionInteractor(
             transitionRepository = fakeKeyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 28bd439..4131145 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -29,6 +29,7 @@
         FromLockscreenTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
index fc740a1..c216945 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -29,6 +29,7 @@
         FromOccludedTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index d72b9c1..42ee152 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -31,6 +31,7 @@
         FromPrimaryBouncerTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
             scope = applicationCoroutineScope,
             bgDispatcher = testDispatcher,
             mainDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..017a9ec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.internalKeyguardTransitionInteractor by
+    Kosmos.Fixture {
+        InternalKeyguardTransitionInteractor(
+            repository = keyguardTransitionRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
index 0667a6b..c6b5ed0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -28,5 +28,6 @@
             keyguardRepository,
             biometricSettingsRepository,
             keyguardTransitionInteractor,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
index 7d8d33f..5836902 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
@@ -30,5 +30,6 @@
             deviceProvisioningInteractor = deviceProvisioningInteractor,
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             repository = keyguardTransitionRepository,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index c90642d..c5da10e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -33,6 +33,6 @@
             fromAodTransitionInteractor = { fromAodTransitionInteractor },
             fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
             fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
-            sceneInteractor = { sceneInteractor }
+            sceneInteractor = sceneInteractor
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index 3c1f7b1..e50e044 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor.scenetransition
 
 import com.android.systemui.keyguard.data.repository.lockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.internalKeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -29,5 +30,6 @@
             applicationScope = applicationCoroutineScope,
             sceneInteractor = sceneInteractor,
             repository = lockscreenSceneTransitionRepository,
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index 0921eb9..ae8b411 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -31,7 +31,7 @@
             repository = sceneContainerRepository,
             logger = sceneLogger,
             sceneFamilyResolvers = { sceneFamilyResolvers },
-            deviceUnlockedInteractor = deviceUnlockedInteractor,
-            keyguardEnabledInteractor = keyguardEnabledInteractor,
+            deviceUnlockedInteractor = { deviceUnlockedInteractor },
+            keyguardEnabledInteractor = { keyguardEnabledInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
index a90a9ff..65016c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.internalKeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -34,5 +35,6 @@
             transitionInteractor = keyguardTransitionInteractor,
             keyguardInteractor = keyguardInteractor,
             deviceUnlockedInteractor = { deviceUnlockedInteractor },
+            internalTransitionInteractor = internalKeyguardTransitionInteractor,
         )
     }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
index 63bcda188..4992c4b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
@@ -447,14 +447,6 @@
     }
 
     @Override
-    public void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
-            String[] receiverPermissions, int appOp, Bundle options,
-            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
-            String initialData, Bundle initialExtras) {
-        throw notSupported();
-    }
-
-    @Override
     public void sendStickyBroadcast(Intent intent) {
         throw notSupported();
     }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c4d38e4..a2bbff0 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -48,3 +48,6 @@
 
 # SystemConfig
 per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
+
+# CertBlocklister
+per-file Cert*.java = tweek@google.com, brambonne@google.com, prb@google.com, miguelaranda@google.com
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index dfd148d..a4026eb 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -241,12 +241,14 @@
      * opportunity to reset any settings depending on our rescue level.
      */
     public static void onSettingsProviderPublished(Context context) {
-        handleNativeRescuePartyResets();
-        ContentResolver contentResolver = context.getContentResolver();
-        DeviceConfig.setMonitorCallback(
-                contentResolver,
-                Executors.newSingleThreadExecutor(),
-                new RescuePartyMonitorCallback(context));
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            handleNativeRescuePartyResets();
+            ContentResolver contentResolver = context.getContentResolver();
+            DeviceConfig.setMonitorCallback(
+                    contentResolver,
+                    Executors.newSingleThreadExecutor(),
+                    new RescuePartyMonitorCallback(context));
+        }
     }
 
 
@@ -256,75 +258,81 @@
      * on modules of newer versions.
      */
     public static void resetDeviceConfigForPackages(List<String> packageNames) {
-        if (packageNames == null) {
-            return;
-        }
-        Set<String> namespacesToReset = new ArraySet<String>();
-        Iterator<String> it = packageNames.iterator();
-        RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated();
-        // Get runtime package to namespace mapping if created.
-        if (rescuePartyObserver != null) {
-            while (it.hasNext()) {
-                String packageName = it.next();
-                Set<String> runtimeAffectedNamespaces =
-                        rescuePartyObserver.getAffectedNamespaceSet(packageName);
-                if (runtimeAffectedNamespaces != null) {
-                    namespacesToReset.addAll(runtimeAffectedNamespaces);
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            if (packageNames == null) {
+                return;
+            }
+            Set<String> namespacesToReset = new ArraySet<String>();
+            Iterator<String> it = packageNames.iterator();
+            RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated();
+            // Get runtime package to namespace mapping if created.
+            if (rescuePartyObserver != null) {
+                while (it.hasNext()) {
+                    String packageName = it.next();
+                    Set<String> runtimeAffectedNamespaces =
+                            rescuePartyObserver.getAffectedNamespaceSet(packageName);
+                    if (runtimeAffectedNamespaces != null) {
+                        namespacesToReset.addAll(runtimeAffectedNamespaces);
+                    }
                 }
             }
-        }
-        // Get preset package to namespace mapping if created.
-        Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages(
-                packageNames);
-        if (presetAffectedNamespaces != null) {
-            namespacesToReset.addAll(presetAffectedNamespaces);
-        }
+            // Get preset package to namespace mapping if created.
+            Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages(
+                    packageNames);
+            if (presetAffectedNamespaces != null) {
+                namespacesToReset.addAll(presetAffectedNamespaces);
+            }
 
-        // Clear flags under the namespaces mapped to these packages.
-        // Using setProperties since DeviceConfig.resetToDefaults bans the current flag set.
-        Iterator<String> namespaceIt = namespacesToReset.iterator();
-        while (namespaceIt.hasNext()) {
-            String namespaceToReset = namespaceIt.next();
-            Properties properties = new Properties.Builder(namespaceToReset).build();
-            try {
-                if (!DeviceConfig.setProperties(properties)) {
-                    logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+            // Clear flags under the namespaces mapped to these packages.
+            // Using setProperties since DeviceConfig.resetToDefaults bans the current flag set.
+            Iterator<String> namespaceIt = namespacesToReset.iterator();
+            while (namespaceIt.hasNext()) {
+                String namespaceToReset = namespaceIt.next();
+                Properties properties = new Properties.Builder(namespaceToReset).build();
+                try {
+                    if (!DeviceConfig.setProperties(properties)) {
+                        logCriticalInfo(Log.ERROR, "Failed to clear properties under "
                             + namespaceToReset
                             + ". Running `device_config get_sync_disabled_for_tests` will confirm"
                             + " if config-bulk-update is enabled.");
+                    }
+                } catch (DeviceConfig.BadConfigException exception) {
+                    logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
+                            + " is already banned, skip reset.");
                 }
-            } catch (DeviceConfig.BadConfigException exception) {
-                logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
-                        + " is already banned, skip reset.");
             }
         }
     }
 
     private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) {
         Set<String> resultSet = new ArraySet<String>();
-        try {
-            String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION,
-                    NAMESPACE_TO_PACKAGE_MAPPING_FLAG, "");
-            String[] mappingEntries = flagVal.split(",");
-            for (int i = 0; i < mappingEntries.length; i++) {
-                if (TextUtils.isEmpty(mappingEntries[i])) {
-                    continue;
-                }
-                String[] splittedEntry = mappingEntries[i].split(":");
-                if (splittedEntry.length != 2) {
-                    throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]);
-                }
-                String namespace = splittedEntry[0];
-                String packageName = splittedEntry[1];
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            try {
+                String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION,
+                        NAMESPACE_TO_PACKAGE_MAPPING_FLAG, "");
+                String[] mappingEntries = flagVal.split(",");
+                for (int i = 0; i < mappingEntries.length; i++) {
+                    if (TextUtils.isEmpty(mappingEntries[i])) {
+                        continue;
+                    }
+                    String[] splitEntry = mappingEntries[i].split(":");
+                    if (splitEntry.length != 2) {
+                        throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]);
+                    }
+                    String namespace = splitEntry[0];
+                    String packageName = splitEntry[1];
 
-                if (packageNames.contains(packageName)) {
-                    resultSet.add(namespace);
+                    if (packageNames.contains(packageName)) {
+                        resultSet.add(namespace);
+                    }
                 }
+            } catch (Exception e) {
+                resultSet.clear();
+                Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e);
+            } finally {
+                return resultSet;
             }
-        } catch (Exception e) {
-            resultSet.clear();
-            Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e);
-        } finally {
+        } else {
             return resultSet;
         }
     }
@@ -342,43 +350,54 @@
         }
 
         public void onNamespaceUpdate(@NonNull String updatedNamespace) {
-            startObservingPackages(mContext, updatedNamespace);
+            if (!Flags.deprecateFlagsAndSettingsResets()) {
+                startObservingPackages(mContext, updatedNamespace);
+            }
         }
 
         public void onDeviceConfigAccess(@NonNull String callingPackage,
                 @NonNull String namespace) {
-            RescuePartyObserver.getInstance(mContext).recordDeviceConfigAccess(
-                            callingPackage,
-                            namespace);
+
+            if (!Flags.deprecateFlagsAndSettingsResets()) {
+                RescuePartyObserver.getInstance(mContext).recordDeviceConfigAccess(
+                        callingPackage,
+                        namespace);
+            }
         }
     }
 
     private static void startObservingPackages(Context context, @NonNull String updatedNamespace) {
-        RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
-        Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet(updatedNamespace);
-        if (callingPackages == null) {
-            return;
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
+            Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet(
+                    updatedNamespace);
+            if (callingPackages == null) {
+                return;
+            }
+            List<String> callingPackageList = new ArrayList<>();
+            callingPackageList.addAll(callingPackages);
+            Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+                    + updatedNamespace);
+            PackageWatchdog.getInstance(context).startObservingHealth(
+                    rescuePartyObserver,
+                    callingPackageList,
+                    DEFAULT_OBSERVING_DURATION_MS);
         }
-        List<String> callingPackageList = new ArrayList<>();
-        callingPackageList.addAll(callingPackages);
-        Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
-                + updatedNamespace);
-        PackageWatchdog.getInstance(context).startObservingHealth(
-                rescuePartyObserver,
-                callingPackageList,
-                DEFAULT_OBSERVING_DURATION_MS);
     }
 
     private static void handleNativeRescuePartyResets() {
-        if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
-            String[] resetNativeCategories = SettingsToPropertiesMapper.getResetNativeCategories();
-            for (int i = 0; i < resetNativeCategories.length; i++) {
-                // Don't let RescueParty reset the namespace for RescueParty switches.
-                if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) {
-                    continue;
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
+                String[] resetNativeCategories =
+                        SettingsToPropertiesMapper.getResetNativeCategories();
+                for (int i = 0; i < resetNativeCategories.length; i++) {
+                    // Don't let RescueParty reset the namespace for RescueParty switches.
+                    if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) {
+                        continue;
+                    }
+                    DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE,
+                            resetNativeCategories[i]);
                 }
-                DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE,
-                        resetNativeCategories[i]);
             }
         }
     }
@@ -400,6 +419,13 @@
         }
     }
 
+    private static int getMaxRescueLevel() {
+        if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+            return Level.factoryReset();
+        }
+        return Level.reboot();
+    }
+
     /**
      * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
      *
@@ -409,19 +435,30 @@
      * @return the rescue level for the n-th mitigation attempt.
      */
     private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
-        if (mitigationCount == 1) {
-            return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
-        } else if (mitigationCount == 2) {
-            return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
-        } else if (mitigationCount == 3) {
-            return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
-        } else if (mitigationCount == 4) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
-        } else if (mitigationCount >= 5) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            if (mitigationCount == 1) {
+                return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
+            } else if (mitigationCount == 2) {
+                return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
+            } else if (mitigationCount == 3) {
+                return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+            } else if (mitigationCount == 4) {
+                return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
+            } else if (mitigationCount >= 5) {
+                return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
+            } else {
+                Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
+                return LEVEL_NONE;
+            }
         } else {
-            Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
-            return LEVEL_NONE;
+            if (mitigationCount == 1) {
+                return Level.reboot();
+            } else if (mitigationCount >= 2) {
+                return Math.min(getMaxRescueLevel(), Level.factoryReset());
+            } else {
+                Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
+                return LEVEL_NONE;
+            }
         }
     }
 
@@ -451,13 +488,13 @@
             return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT);
         } else if (mitigationCount == 4) {
             return Math.min(getMaxRescueLevel(mayPerformReboot),
-                                RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+                    RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
         } else if (mitigationCount == 5) {
             return Math.min(getMaxRescueLevel(mayPerformReboot),
-                                RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
+                    RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
         } else if (mitigationCount == 6) {
             return Math.min(getMaxRescueLevel(mayPerformReboot),
-                                RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
+                    RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
         } else if (mitigationCount >= 7) {
             return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET);
         } else {
@@ -465,6 +502,22 @@
         }
     }
 
+    /**
+     * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
+     *
+     * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
+     * @return the rescue level for the n-th mitigation attempt.
+     */
+    private static @RescueLevels int getRescueLevel(int mitigationCount) {
+        if (mitigationCount == 1) {
+            return Level.reboot();
+        } else if (mitigationCount >= 2) {
+            return Math.min(getMaxRescueLevel(), Level.factoryReset());
+        } else {
+            return Level.none();
+        }
+    }
+
     private static void executeRescueLevel(Context context, @Nullable String failedPackage,
             int level) {
         Slog.w(TAG, "Attempting rescue level " + levelToString(level));
@@ -537,13 +590,22 @@
                 executeWarmReboot(context, level, failedPackage);
                 break;
             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, level);
+                if (!Flags.deprecateFlagsAndSettingsResets()) {
+                    resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
+                            level);
+                }
                 break;
             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, level);
+                if (!Flags.deprecateFlagsAndSettingsResets()) {
+                    resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES,
+                            level);
+                }
                 break;
             case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, level);
+                if (!Flags.deprecateFlagsAndSettingsResets()) {
+                    resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS,
+                            level);
+                }
                 break;
             case RESCUE_LEVEL_FACTORY_RESET:
                 // Before the completion of Reboot, if any crash happens then PackageWatchdog
@@ -560,6 +622,12 @@
 
     private static void executeWarmReboot(Context context, int level,
             @Nullable String failedPackage) {
+        if (Flags.deprecateFlagsAndSettingsResets()) {
+            if (shouldThrottleReboot()) {
+                return;
+            }
+        }
+
         // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
         // when device shutting down.
         setRebootProperty(true);
@@ -579,6 +647,11 @@
 
     private static void executeFactoryReset(Context context, int level,
             @Nullable String failedPackage) {
+        if (Flags.deprecateFlagsAndSettingsResets()) {
+            if (shouldThrottleReboot()) {
+                return;
+            }
+        }
         setFactoryResetProperty(true);
         long now = System.currentTimeMillis();
         setLastFactoryResetTimeMs(now);
@@ -655,30 +728,32 @@
 
     private static void resetAllSettingsIfNecessary(Context context, int mode,
             int level) throws Exception {
-        // No need to reset Settings again if they are already reset in the current level once.
-        if (getMaxRescueLevelAttempted() >= level) {
-            return;
-        }
-        setMaxRescueLevelAttempted(level);
-        // Try our best to reset all settings possible, and once finished
-        // rethrow any exception that we encountered
-        Exception res = null;
-        final ContentResolver resolver = context.getContentResolver();
-        try {
-            Settings.Global.resetToDefaultsAsUser(resolver, null, mode,
-                    UserHandle.SYSTEM.getIdentifier());
-        } catch (Exception e) {
-            res = new RuntimeException("Failed to reset global settings", e);
-        }
-        for (int userId : getAllUserIds()) {
-            try {
-                Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
-            } catch (Exception e) {
-                res = new RuntimeException("Failed to reset secure settings for " + userId, e);
+        if (!Flags.deprecateFlagsAndSettingsResets()) {
+            // No need to reset Settings again if they are already reset in the current level once.
+            if (getMaxRescueLevelAttempted() >= level) {
+                return;
             }
-        }
-        if (res != null) {
-            throw res;
+            setMaxRescueLevelAttempted(level);
+            // Try our best to reset all settings possible, and once finished
+            // rethrow any exception that we encountered
+            Exception res = null;
+            final ContentResolver resolver = context.getContentResolver();
+            try {
+                Settings.Global.resetToDefaultsAsUser(resolver, null, mode,
+                        UserHandle.SYSTEM.getIdentifier());
+            } catch (Exception e) {
+                res = new RuntimeException("Failed to reset global settings", e);
+            }
+            for (int userId : getAllUserIds()) {
+                try {
+                    Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
+                } catch (Exception e) {
+                    res = new RuntimeException("Failed to reset secure settings for " + userId, e);
+                }
+            }
+            if (res != null) {
+                throw res;
+            }
         }
     }
 
@@ -731,11 +806,15 @@
             if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
                 if (Flags.recoverabilityDetection()) {
-                    return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                            mayPerformReboot(failedPackage), failedPackage));
+                    if (!Flags.deprecateFlagsAndSettingsResets()) {
+                        return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+                                mayPerformReboot(failedPackage), failedPackage));
+                    } else {
+                        return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+                    }
                 } else {
                     return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                        mayPerformReboot(failedPackage)));
+                            mayPerformReboot(failedPackage)));
                 }
             } else {
                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
@@ -750,10 +829,17 @@
             }
             if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
-                final int level = Flags.recoverabilityDetection() ? getRescueLevel(mitigationCount,
-                        mayPerformReboot(failedPackage), failedPackage)
-                        : getRescueLevel(mitigationCount,
-                                mayPerformReboot(failedPackage));
+                final int level;
+                if (Flags.recoverabilityDetection()) {
+                    if (!Flags.deprecateFlagsAndSettingsResets()) {
+                        level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage),
+                                failedPackage);
+                    } else {
+                        level = getRescueLevel(mitigationCount);
+                    }
+                } else {
+                    level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage));
+                }
                 executeRescueLevel(mContext,
                         failedPackage == null ? null : failedPackage.getPackageName(), level);
                 return true;
@@ -787,8 +873,12 @@
                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
             if (Flags.recoverabilityDetection()) {
-                return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                        true, /*failedPackage=*/ null));
+                if (!Flags.deprecateFlagsAndSettingsResets()) {
+                    return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+                            true, /*failedPackage=*/ null));
+                } else {
+                    return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+                }
             } else {
                 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
             }
@@ -800,9 +890,17 @@
                 return false;
             }
             boolean mayPerformReboot = !shouldThrottleReboot();
-            final int level = Flags.recoverabilityDetection() ? getRescueLevel(mitigationCount,
-                        mayPerformReboot, /*failedPackage=*/ null)
-                        : getRescueLevel(mitigationCount, mayPerformReboot);
+            final int level;
+            if (Flags.recoverabilityDetection()) {
+                if (!Flags.deprecateFlagsAndSettingsResets()) {
+                    level = getRescueLevel(mitigationCount, mayPerformReboot,
+                            /*failedPackage=*/ null);
+                } else {
+                    level = getRescueLevel(mitigationCount);
+                }
+            } else {
+                level = getRescueLevel(mitigationCount, mayPerformReboot);
+            }
             executeRescueLevel(mContext, /*failedPackage=*/ null, level);
             return true;
         }
@@ -828,18 +926,6 @@
             return isPersistentSystemApp(failingPackage.getPackageName());
         }
 
-        /**
-         * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
-         * Will return {@code false} if a factory reset was already offered recently.
-         */
-        private boolean shouldThrottleReboot() {
-            Long lastResetTime = getLastFactoryResetTimeMs();
-            long now = System.currentTimeMillis();
-            long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
-                    DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
-            return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
-        }
-
         private boolean isPersistentSystemApp(@NonNull String packageName) {
             PackageManager pm = mContext.getPackageManager();
             try {
@@ -852,20 +938,22 @@
 
         private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
                 @NonNull String namespace) {
-            // Record it in calling packages to namespace map
-            Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage);
-            if (namespaceSet == null) {
-                namespaceSet = new ArraySet<>();
-                mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet);
+            if (!Flags.deprecateFlagsAndSettingsResets()) {
+                // Record it in calling packages to namespace map
+                Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage);
+                if (namespaceSet == null) {
+                    namespaceSet = new ArraySet<>();
+                    mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet);
+                }
+                namespaceSet.add(namespace);
+                // Record it in namespace to calling packages map
+                Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace);
+                if (callingPackageSet == null) {
+                    callingPackageSet = new ArraySet<>();
+                }
+                callingPackageSet.add(callingPackage);
+                mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet);
             }
-            namespaceSet.add(namespace);
-            // Record it in namespace to calling packages map
-            Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace);
-            if (callingPackageSet == null) {
-                callingPackageSet = new ArraySet<>();
-            }
-            callingPackageSet.add(callingPackage);
-            mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet);
         }
 
         private synchronized Set<String> getAffectedNamespaceSet(String failedPackage) {
@@ -881,6 +969,18 @@
         }
     }
 
+    /**
+     * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
+     * Will return {@code false} if a factory reset was already offered recently.
+     */
+    private static boolean shouldThrottleReboot() {
+        Long lastResetTime = getLastFactoryResetTimeMs();
+        long now = System.currentTimeMillis();
+        long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+                DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+        return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
+    }
+
     private static int[] getAllUserIds() {
         int systemUserId = UserHandle.SYSTEM.getIdentifier();
         int[] userIds = { systemUserId };
@@ -919,6 +1019,22 @@
         }
     }
 
+    private static class Level {
+        static int none() {
+            return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE;
+        }
+
+        static int reboot() {
+            return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT;
+        }
+
+        static int factoryReset() {
+            return Flags.recoverabilityDetection()
+                    ? RESCUE_LEVEL_FACTORY_RESET
+                    : LEVEL_FACTORY_RESET;
+        }
+    }
+
     private static String levelToString(int level) {
         if (Flags.recoverabilityDetection()) {
             switch (level) {
diff --git a/services/core/java/com/android/server/am/LmkdConnection.java b/services/core/java/com/android/server/am/LmkdConnection.java
index 598f086..4faadcb 100644
--- a/services/core/java/com/android/server/am/LmkdConnection.java
+++ b/services/core/java/com/android/server/am/LmkdConnection.java
@@ -91,10 +91,18 @@
     @GuardedBy("mLmkdSocketLock")
     private LocalSocket mLmkdSocket = null;
 
-    // socket I/O streams
-    @GuardedBy("mLmkdSocketLock")
+    // mutex to synchronize socket output stream with socket creation/destruction
+    private final Object mLmkdOutputStreamLock = new Object();
+
+    // socket output stream
+    @GuardedBy("mLmkdOutputStreamLock")
     private OutputStream mLmkdOutputStream = null;
-    @GuardedBy("mLmkdSocketLock")
+
+    // mutex to synchronize socket input stream with socket creation/destruction
+    private final Object mLmkdInputStreamLock = new Object();
+
+    // socket input stream
+    @GuardedBy("mLmkdInputStreamLock")
     private InputStream mLmkdInputStream = null;
 
     // buffer to store incoming data
@@ -148,9 +156,13 @@
                 return false;
             }
             // connection established
-            mLmkdSocket = socket;
-            mLmkdOutputStream = ostream;
-            mLmkdInputStream = istream;
+            synchronized(mLmkdOutputStreamLock) {
+                synchronized(mLmkdInputStreamLock) {
+                    mLmkdSocket = socket;
+                    mLmkdOutputStream = ostream;
+                    mLmkdInputStream = istream;
+                }
+            }
             mMsgQueue.addOnFileDescriptorEventListener(mLmkdSocket.getFileDescriptor(),
                 EVENT_INPUT | EVENT_ERROR,
                 new MessageQueue.OnFileDescriptorEventListener() {
@@ -177,7 +189,13 @@
                 mMsgQueue.removeOnFileDescriptorEventListener(
                         mLmkdSocket.getFileDescriptor());
                 IoUtils.closeQuietly(mLmkdSocket);
-                mLmkdSocket = null;
+                synchronized(mLmkdOutputStreamLock) {
+                    synchronized(mLmkdInputStreamLock) {
+                        mLmkdOutputStream = null;
+                        mLmkdInputStream = null;
+                        mLmkdSocket = null;
+                    }
+                }
             }
             // wake up reply waiters if any
             synchronized (mReplyBufLock) {
@@ -262,24 +280,33 @@
     }
 
     private boolean write(ByteBuffer buf) {
-        synchronized (mLmkdSocketLock) {
-            try {
-                mLmkdOutputStream.write(buf.array(), 0, buf.position());
-            } catch (IOException ex) {
-                return false;
+        boolean result = false;
+
+        synchronized(mLmkdOutputStreamLock) {
+            if (mLmkdOutputStream != null) {
+                try {
+                    mLmkdOutputStream.write(buf.array(), 0, buf.position());
+                    result = true;
+                } catch (IOException ex) {
+                }
             }
-            return true;
         }
+
+        return result;
     }
 
     private int read(ByteBuffer buf) {
-        synchronized (mLmkdSocketLock) {
-            try {
-                return mLmkdInputStream.read(buf.array(), 0, buf.array().length);
-            } catch (IOException ex) {
+        int result = -1;
+
+        synchronized(mLmkdInputStreamLock) {
+            if (mLmkdInputStream != null) {
+                try {
+                    result = mLmkdInputStream.read(buf.array(), 0, buf.array().length);
+                } catch (IOException ex) {
+                }
             }
-            return -1;
         }
+        return result;
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index d061e2d..fbd32a6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -31,7 +31,6 @@
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import java.util.function.Supplier;
 
@@ -203,16 +202,6 @@
         }
     }
 
-    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
-    protected final void resetIgnoreDisplayTouches() {
-        final AidlSession session = (AidlSession) getFreshDaemon();
-        try {
-            session.getSession().setIgnoreDisplayTouches(false);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when resetting setIgnoreDisplayTouches");
-        }
-    }
-
     @Override
     public boolean isInterruptable() {
         return true;
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 4c86f57..60cfd5a 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
@@ -962,6 +962,19 @@
             provider.onUdfpsUiEvent(event, requestId, sensorId);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches) {
+            super.setIgnoreDisplayTouches_enforcePermission();
+
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+            if (provider == null) {
+                Slog.w(TAG,
+                        "No matching provider for setIgnoreDisplayTouches, sensorId: " + sensorId);
+                return;
+            }
+            provider.setIgnoreDisplayTouches(requestId, sensorId, ignoreTouches);
+        }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index a6cf2f4..e4a99e6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -134,6 +134,8 @@
 
     void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
 
+    void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches);
+
     void onPowerPressed();
 
     @NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index dce0175..15d7a47 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -31,4 +31,5 @@
     void onPointerUp(PointerContext pc);
     void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event);
     boolean isPointerDown();
+    void setIgnoreDisplayTouches(boolean ignoreTouches);
 }
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 72d92b9..d04afdb 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
@@ -33,7 +33,6 @@
 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.events.AuthenticationAcquiredInfo;
 import android.hardware.biometrics.events.AuthenticationErrorInfo;
 import android.hardware.biometrics.events.AuthenticationFailedInfo;
@@ -182,7 +181,6 @@
         handleLockout(authenticated);
         if (authenticated) {
             mState = STATE_STOPPED;
-            resetIgnoreDisplayTouches();
             mSensorOverlays.hide(getSensorId());
             if (reportBiometricAuthAttempts()) {
                 mAuthenticationStateListeners.onAuthenticationSucceeded(
@@ -223,7 +221,6 @@
                 // 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.
-                resetIgnoreDisplayTouches();
                 mSensorOverlays.hide(getSensorId());
                 mAuthenticationStateListeners.onAuthenticationError(
                         new AuthenticationErrorInfo.Builder(BiometricSourceType.FINGERPRINT,
@@ -275,7 +272,6 @@
             BiometricNotificationUtils.showBadCalibrationNotification(getContext());
         }
 
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo
                 .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build()
@@ -284,7 +280,6 @@
 
     @Override
     protected void startHalOperation() {
-        resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(), getRequestReason(), this);
         mAuthenticationStateListeners.onAuthenticationStarted(new AuthenticationStartedInfo
                 .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build()
@@ -331,12 +326,6 @@
             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);
                 }
@@ -353,7 +342,6 @@
 
     @Override
     protected void stopHalOperation() {
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo
                 .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build()
@@ -415,6 +403,15 @@
     }
 
     @Override
+    public void setIgnoreDisplayTouches(boolean ignoreTouches) {
+        try {
+            getFreshDaemon().getSession().setIgnoreDisplayTouches(ignoreTouches);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    @Override
     public boolean isPointerDown() {
         return mIsPointerDown;
     }
@@ -457,7 +454,6 @@
             Slog.e(TAG, "Remote exception", e);
         }
 
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo
                 .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build()
@@ -492,7 +488,6 @@
             Slog.e(TAG, "Remote exception", e);
         }
 
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo
                 .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build()
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 36af5db..fb48053 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
@@ -87,7 +87,6 @@
 
     @Override
     protected void stopHalOperation() {
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(
                 new AuthenticationStoppedInfo.Builder(BiometricSourceType.FINGERPRINT,
@@ -107,7 +106,6 @@
 
     @Override
     protected void startHalOperation() {
-        resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
                 this);
         mAuthenticationStateListeners.onAuthenticationStarted(
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 3a72d7e..993a68f 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
@@ -150,7 +150,6 @@
                 controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
 
         if (remaining == 0) {
-            resetIgnoreDisplayTouches();
             mSensorOverlays.hide(getSensorId());
             mAuthenticationStateListeners.onAuthenticationStopped(
                     new AuthenticationStoppedInfo.Builder(
@@ -211,7 +210,6 @@
         );
         super.onError(errorCode, vendorCode);
 
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(
                 new AuthenticationStoppedInfo.Builder(BiometricSourceType.FINGERPRINT,
@@ -227,7 +225,6 @@
 
     @Override
     protected void startHalOperation() {
-        resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(),
                 getRequestReasonFromFingerprintEnrollReason(mEnrollReason), this);
         mAuthenticationStateListeners.onAuthenticationStarted(new AuthenticationStartedInfo
@@ -277,7 +274,6 @@
 
     @Override
     protected void stopHalOperation() {
-        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo
                 .Builder(BiometricSourceType.FINGERPRINT,
@@ -359,5 +355,14 @@
     }
 
     @Override
+    public void setIgnoreDisplayTouches(boolean ignoreTouches) {
+        try {
+            getFreshDaemon().getSession().setIgnoreDisplayTouches(ignoreTouches);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to send setIgnoreDisplayTouches", e);
+        }
+    }
+
+    @Override
     public void onPowerPressed() {}
 }
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 1bddb83b..12baf00 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
@@ -790,6 +790,19 @@
     }
 
     @Override
+    public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches) {
+        mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+                requestId, (client) -> {
+                    if (!(client instanceof Udfps)) {
+                        Slog.e(getTag(),
+                                "setIgnoreDisplayTouches received during client: " + client);
+                        return;
+                    }
+                    ((Udfps) client).setIgnoreDisplayTouches(ignoreTouches);
+                });
+    }
+
+    @Override
     public void onPowerPressed() {
         for (int i = 0; i < mFingerprintSensors.size(); i++) {
             final Sensor sensor = mFingerprintSensors.valueAt(i);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5f1bc2a..6928b33 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1947,6 +1947,27 @@
         }
     }
 
+    private void setVirtualDisplayRotationInternal(IBinder appToken,
+            @Surface.Rotation int rotation) {
+        int displayId;
+        synchronized (mSyncRoot) {
+            if (mVirtualDisplayAdapter == null) {
+                return;
+            }
+            DisplayDevice device = mVirtualDisplayAdapter.getDisplayDevice(appToken);
+            if (device == null) {
+                return;
+            }
+            LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
+            if (display == null) {
+                return;
+            }
+            displayId = display.getDisplayIdLocked();
+        }
+        mWindowManagerInternal.setNonDefaultDisplayRotation(
+                displayId, rotation, /* caller= */ "Virtual Display");
+    }
+
     private void registerDefaultDisplayAdapters() {
         // Register default display adapters.
         synchronized (mSyncRoot) {
@@ -4199,6 +4220,20 @@
         }
 
         @Override // Binder call
+        public void setVirtualDisplayRotation(IVirtualDisplayCallback callback,
+                @Surface.Rotation int rotation) {
+            if (!android.companion.virtualdevice.flags.Flags.virtualDisplayRotationApi()) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setVirtualDisplayRotationInternal(callback.asBinder(), rotation);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void dump(@NonNull FileDescriptor fd, @NonNull final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 1a5c79f..9b02f4b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -56,6 +56,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayCutout;
 import android.view.DisplayShape;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -213,6 +214,10 @@
         }
     }
 
+    DisplayDevice getDisplayDevice(IBinder appToken) {
+        return mVirtualDisplayDevices.get(appToken);
+    }
+
     /**
      * Generates a virtual display's unique identifier.
      *
@@ -271,6 +276,7 @@
         private boolean mIsDisplayOn;
         private int mDisplayIdToMirror;
         private boolean mIsWindowManagerMirroring;
+        private DisplayCutout mDisplayCutout;
 
         public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
                 int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -286,6 +292,7 @@
             mHeight = virtualDisplayConfig.getHeight();
             mDensityDpi = virtualDisplayConfig.getDensityDpi();
             mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
+            mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
             mMode = createMode(mWidth, mHeight, getRefreshRate());
             mSurface = surface;
             mFlags = flags;
@@ -567,6 +574,7 @@
 
                 mInfo.displayShape =
                         DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
+                mInfo.displayCutout = mDisplayCutout;
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 0f9c344..49888db 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -597,6 +597,12 @@
     @Constants.HandleMessageResult
     protected int handleReportPhysicalAddress(HdmiCecMessage message) {
         super.handleReportPhysicalAddress(message);
+        // Ignore <Report Physical Address> while DeviceDiscoveryAction is in progress to avoid
+        // starting a NewDeviceAction which might interfere in creating the list of known devices.
+        if (hasAction(DeviceDiscoveryAction.class)) {
+            return Constants.HANDLED;
+        }
+
         int path = HdmiUtils.twoBytesToInt(message.getParams());
         int address = message.getSource();
         int type = message.getParams()[2];
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bef984b..1f46af8 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3364,6 +3364,10 @@
         mPointerIconCache.setPointerFillStyle(fillStyle);
     }
 
+    void setPointerStrokeStyle(@PointerIcon.PointerIconVectorStyleStroke int strokeStyle) {
+        mPointerIconCache.setPointerStrokeStyle(strokeStyle);
+    }
+
     void setPointerScale(float scale) {
         mPointerIconCache.setPointerScale(scale);
     }
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 593b091..000f312 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -18,6 +18,7 @@
 
 import static android.view.PointerIcon.DEFAULT_POINTER_SCALE;
 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
+import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
 import static android.view.flags.Flags.enableVectorCursorA11ySettings;
 
 import static com.android.input.flags.Flags.rateLimitUserActivityPokeInDispatcher;
@@ -103,6 +104,8 @@
                         (reason) -> updateStylusPointerIconEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.POINTER_FILL_STYLE),
                         (reason) -> updatePointerFillStyleFromSettings()),
+                Map.entry(Settings.System.getUriFor(Settings.System.POINTER_STROKE_STYLE),
+                        (reason) -> updatePointerStrokeStyleFromSettings()),
                 Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SCALE),
                         (reason) -> updatePointerScaleFromSettings()));
     }
@@ -281,6 +284,17 @@
         mService.setPointerFillStyle(pointerFillStyle);
     }
 
+    private void updatePointerStrokeStyleFromSettings() {
+        if (!enableVectorCursorA11ySettings()) {
+            return;
+        }
+        final int pointerStrokeStyle = Settings.System.getIntForUser(
+                mContext.getContentResolver(), Settings.System.POINTER_STROKE_STYLE,
+                POINTER_ICON_VECTOR_STYLE_STROKE_WHITE,
+                UserHandle.USER_CURRENT);
+        mService.setPointerStrokeStyle(pointerStrokeStyle);
+    }
+
     private void updatePointerScaleFromSettings() {
         if (!enableVectorCursorA11ySettings()) {
             return;
diff --git a/services/core/java/com/android/server/input/PointerIconCache.java b/services/core/java/com/android/server/input/PointerIconCache.java
index 44622d8..297cd68 100644
--- a/services/core/java/com/android/server/input/PointerIconCache.java
+++ b/services/core/java/com/android/server/input/PointerIconCache.java
@@ -18,6 +18,7 @@
 
 import static android.view.PointerIcon.DEFAULT_POINTER_SCALE;
 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
+import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -65,6 +66,9 @@
     private @PointerIcon.PointerIconVectorStyleFill int mPointerIconFillStyle =
             POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    private @PointerIcon.PointerIconVectorStyleStroke int mPointerIconStrokeStyle =
+            POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
     private float mPointerIconScale = DEFAULT_POINTER_SCALE;
 
     private final DisplayManager.DisplayListener mDisplayListener =
@@ -120,6 +124,11 @@
         mUiThreadHandler.post(() -> handleSetPointerFillStyle(fillStyle));
     }
 
+    /** Set the stroke style for vector pointer icons. */
+    public void setPointerStrokeStyle(@PointerIcon.PointerIconVectorStyleStroke int strokeStyle) {
+        mUiThreadHandler.post(() -> handleSetPointerStrokeStyle(strokeStyle));
+    }
+
     /** Set the scale for vector pointer icons. */
     public void setPointerScale(float scale) {
         mUiThreadHandler.post(() -> handleSetPointerScale(scale));
@@ -144,6 +153,8 @@
                 theme.setTo(context.getTheme());
                 theme.applyStyle(PointerIcon.vectorFillStyleToResource(mPointerIconFillStyle),
                         /* force= */ true);
+                theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(mPointerIconStrokeStyle),
+                        /* force= */ true);
                 icon = PointerIcon.getLoadedSystemIcon(new ContextThemeWrapper(context, theme),
                         type, mUseLargePointerIcons, mPointerIconScale);
                 iconsByType.put(type, icon);
@@ -224,6 +235,20 @@
     }
 
     @android.annotation.UiThread
+    private void handleSetPointerStrokeStyle(
+            @PointerIcon.PointerIconVectorStyleStroke int strokeStyle) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            if (mPointerIconStrokeStyle == strokeStyle) {
+                return;
+            }
+            mPointerIconStrokeStyle = strokeStyle;
+            // Clear all cached icons on all displays.
+            mLoadedPointerIconsByDisplayAndType.clear();
+        }
+        mNative.reloadPointerIcons();
+    }
+
+    @android.annotation.UiThread
     private void handleSetPointerScale(float scale) {
         synchronized (mLoadedPointerIconsByDisplayAndType) {
             if (mPointerIconScale == scale) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 89b320d..fbb6ccf 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -195,6 +195,7 @@
 import java.security.InvalidParameterException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -298,22 +299,21 @@
     private final String[] mNonPreemptibleInputMethods;
 
     /**
-     * See {@link #shouldEnableExperimentalConcurrentMultiUserMode(Context)} about when set to be
-     * {@code true}.
+     * See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
      */
     @SharedByAllUsersField
-    private final boolean mExperimentalConcurrentMultiUserModeEnabled;
+    private final boolean mConcurrentMultiUserModeEnabled;
 
     /**
-     * Returns {@code true} if experimental concurrent multi-user mode is enabled.
+     * Returns {@code true} if the concurrent multi-user mode is enabled.
      *
      * <p>Currently not compatible with profiles (e.g. work profile).</p>
      *
      * @param context {@link Context} to be used to query
      *                {@link PackageManager#FEATURE_AUTOMOTIVE}
-     * @return {@code true} if experimental concurrent multi-user mode is enabled.
+     * @return {@code true} if the concurrent multi-user mode is enabled.
      */
-    static boolean shouldEnableExperimentalConcurrentMultiUserMode(@NonNull Context context) {
+    static boolean shouldEnableConcurrentMultiUserMode(@NonNull Context context) {
         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
                 && UserManager.isVisibleBackgroundUsersEnabled()
                 && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled)
@@ -330,7 +330,7 @@
     @UserIdInt
     @BinderThread
     private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) {
-        return mExperimentalConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
+        return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
     }
 
     final Context mContext;
@@ -571,10 +571,6 @@
     private final ImeTrackerService mImeTrackerService;
 
     class SettingsObserver extends ContentObserver {
-        int mUserId;
-        boolean mRegistered = false;
-        @NonNull
-        String mLastEnabled = "";
 
         /**
          * <em>This constructor must be called within the lock.</em>
@@ -583,37 +579,29 @@
             super(handler);
         }
 
-        @GuardedBy("ImfLock.class")
-        public void registerContentObserverLocked(@UserIdInt int userId) {
-            if (mRegistered && mUserId == userId) {
-                return;
-            }
+        void registerContentObserverForAllUsers() {
             ContentResolver resolver = mContext.getContentResolver();
-            if (mRegistered) {
-                mContext.getContentResolver().unregisterContentObserver(this);
-                mRegistered = false;
-            }
-            if (mUserId != userId) {
-                mLastEnabled = "";
-                mUserId = userId;
-            }
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    STYLUS_HANDWRITING_ENABLED), false, this);
-            mRegistered = true;
+            resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
+                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this, UserHandle.ALL);
+            resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
+                    Settings.Secure.ENABLED_INPUT_METHODS), false, this, UserHandle.ALL);
+            resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
+                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, UserHandle.ALL);
+            resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
+                    Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, UserHandle.ALL);
+            resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
+                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, UserHandle.ALL);
+            resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
+                    STYLUS_HANDWRITING_ENABLED), false, this, UserHandle.ALL);
         }
 
         @Override
-        public void onChange(boolean selfChange, Uri uri) {
+        public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags,
+                @UserIdInt int userId) {
+            uris.forEach(uri -> onChangeInternal(uri, userId));
+        }
+
+        private void onChangeInternal(@NonNull Uri uri, @UserIdInt int userId) {
             final Uri showImeUri = Settings.Secure.getUriFor(
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
@@ -621,23 +609,27 @@
             final Uri stylusHandwritingEnabledUri = Settings.Secure.getUriFor(
                     STYLUS_HANDWRITING_ENABLED);
             synchronized (ImfLock.class) {
+                if (!mConcurrentMultiUserModeEnabled && mCurrentUserId != userId) {
+                    return;
+                }
+
                 if (showImeUri.equals(uri)) {
                     mMenuController.updateKeyboardFromSettingsLocked();
                 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
                     final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
                             mContext.getContentResolver(),
-                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
+                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, userId);
                     mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
                             accessibilitySoftKeyboardSetting);
-                    final var userData = getUserData(mUserId);
+                    final var userData = getUserData(userId);
                     if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
                                 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE,
-                                mUserId);
-                    } else if (isShowRequestedForCurrentWindow(mUserId)) {
+                                userId);
+                    } else if (isShowRequestedForCurrentWindow(userId)) {
                         showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
                                 InputMethodManager.SHOW_IMPLICIT,
-                                SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, mUserId);
+                                SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
                     }
                 } else if (stylusHandwritingEnabledUri.equals(uri)) {
                     InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
@@ -645,22 +637,17 @@
                             .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
-                    String newEnabled = InputMethodSettingsRepository.get(mUserId)
+                    String newEnabled = InputMethodSettingsRepository.get(userId)
                             .getEnabledInputMethodsStr();
-                    if (!mLastEnabled.equals(newEnabled)) {
-                        mLastEnabled = newEnabled;
+                    final var userData = getUserData(userId);
+                    if (!userData.mLastEnabledInputMethodsStr.equals(newEnabled)) {
+                        userData.mLastEnabledInputMethodsStr = newEnabled;
                         enabledChanged = true;
                     }
-                    updateInputMethodsFromSettingsLocked(enabledChanged, mUserId);
+                    updateInputMethodsFromSettingsLocked(enabledChanged, userId);
                 }
             }
         }
-
-        @Override
-        public String toString() {
-            return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
-                    + " mLastEnabled=" + mLastEnabled + "}";
-        }
     }
 
     /**
@@ -973,7 +960,7 @@
 
         public Lifecycle(Context context) {
             this(context, new InputMethodManagerService(context,
-                            shouldEnableExperimentalConcurrentMultiUserMode(context)));
+                            shouldEnableConcurrentMultiUserMode(context)));
         }
 
         public Lifecycle(
@@ -1003,7 +990,7 @@
         public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
             // Called on ActivityManager thread.
             synchronized (ImfLock.class) {
-                if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
+                if (mService.mConcurrentMultiUserModeEnabled) {
                     // In concurrent multi-user mode, we in general do not rely on the concept of
                     // current user.
                     return;
@@ -1037,9 +1024,9 @@
             SecureSettingsWrapper.onUserStarting(userId);
             synchronized (ImfLock.class) {
                 mService.getUserData(userId);
-                if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
+                if (mService.mConcurrentMultiUserModeEnabled) {
                     if (mService.mCurrentUserId != userId && mService.mSystemReady) {
-                        mService.experimentalInitializeVisibleBackgroundUserLocked(userId);
+                        mService.initializeVisibleBackgroundUserLocked(userId);
                     }
                 }
             }
@@ -1062,8 +1049,8 @@
                 // We need to rebuild IMEs.
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
-            } else if (mExperimentalConcurrentMultiUserModeEnabled) {
-                experimentalInitializeVisibleBackgroundUserLocked(userId);
+            } else if (mConcurrentMultiUserModeEnabled) {
+                initializeVisibleBackgroundUserLocked(userId);
             }
         }
     }
@@ -1090,20 +1077,19 @@
     }
 
     public InputMethodManagerService(Context context,
-            boolean experimentalConcurrentMultiUserModeEnabled) {
-        this(context, experimentalConcurrentMultiUserModeEnabled, null, null, null);
+            boolean concurrentMultiUserModeEnabled) {
+        this(context, concurrentMultiUserModeEnabled, null, null, null);
     }
 
     @VisibleForTesting
     InputMethodManagerService(
             Context context,
-            boolean experimentalConcurrentMultiUserModeEnabled,
+            boolean concurrentMultiUserModeEnabled,
             @Nullable ServiceThread serviceThreadForTesting,
             @Nullable ServiceThread ioThreadForTesting,
             @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
         synchronized (ImfLock.class) {
-            mExperimentalConcurrentMultiUserModeEnabled =
-                    experimentalConcurrentMultiUserModeEnabled;
+            mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled;
             mContext = context;
             mRes = context.getResources();
             SecureSettingsWrapper.onStart(mContext);
@@ -1307,11 +1293,12 @@
 
         maybeInitImeNavbarConfigLocked(newUserId);
 
-        // ContentObserver should be registered again when the user is changed
-        mSettingsObserver.registerContentObserverLocked(newUserId);
+        final var newUserData = getUserData(newUserId);
+
+        // TODO(b/342027196): Double check if we need to always reset upon user switching.
+        newUserData.mLastEnabledInputMethodsStr = "";
 
         mCurrentUserId = newUserId;
-        final var newUserData = getUserData(newUserId);
         final String defaultImiId = SecureSettingsWrapper.getString(
                 Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
 
@@ -1402,7 +1389,7 @@
                 }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
 
                 mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
-                mSettingsObserver.registerContentObserverLocked(currentUserId);
+                mSettingsObserver.registerContentObserverForAllUsers();
 
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
                 broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -1428,10 +1415,10 @@
                         AdditionalSubtypeMapRepository::startWriterThread,
                         "Start AdditionalSubtypeMapRepository's writer thread");
 
-                if (mExperimentalConcurrentMultiUserModeEnabled) {
+                if (mConcurrentMultiUserModeEnabled) {
                     for (int userId : mUserManagerInternal.getUserIds()) {
                         if (userId != mCurrentUserId) {
-                            experimentalInitializeVisibleBackgroundUserLocked(userId);
+                            initializeVisibleBackgroundUserLocked(userId);
                         }
                     }
                 }
@@ -2538,7 +2525,7 @@
             @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> {
                 // TODO(b/305849394): Figure out what we should do for single user IME mode.
                 final boolean shouldClearClientSession =
-                        !mExperimentalConcurrentMultiUserModeEnabled
+                        !mConcurrentMultiUserModeEnabled
                                 || UserHandle.getUserId(c.mUid) == userId;
                 if (shouldClearClientSession) {
                     clearClientSessionLocked(c);
@@ -2840,27 +2827,25 @@
     }
 
     /**
-     * This is an experimental implementation used when and only when
-     * {@link #mExperimentalConcurrentMultiUserModeEnabled}.
+     * This initialization logic is used when and only when {@link #mConcurrentMultiUserModeEnabled}
+     * is set to {@code true}.
      *
-     * <p>Never assume what this method is doing is officially supported. For the canonical and
-     * desired behaviors always refer to single-user code paths such as
+     * <p>There remain several yet-to-be-implemented features. For the canonical and desired
+     * behaviors always refer to single-user code paths such as
      * {@link #updateInputMethodsFromSettingsLocked(boolean, int)}.</p>
      *
      * <p>Here are examples of missing features.</p>
      * <ul>
-     *     <li>Subtypes are not supported at all!</li>
      *     <li>Profiles are not supported.</li>
      *     <li>
      *         {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
      *     </li>
      *     <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
-     *     <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li>
      *     <li>and so on.</li>
      * </ul>
      */
     @GuardedBy("ImfLock.class")
-    void experimentalInitializeVisibleBackgroundUserLocked(@UserIdInt int userId) {
+    void initializeVisibleBackgroundUserLocked(@UserIdInt int userId) {
         final var settings = InputMethodSettingsRepository.get(userId);
 
         // Until we figure out what makes most sense, we enable all the pre-installed IMEs in
@@ -2868,7 +2853,7 @@
         String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
         for (var imi : settings.getMethodList()) {
             if (!imi.isSystem()) {
-                return;
+                continue;
             }
             enabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(enabledImeIdsStr, imi.getId());
         }
@@ -2881,19 +2866,18 @@
         if (TextUtils.isEmpty(id)) {
             final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
                     settings.getEnabledInputMethodList());
-            if (imi == null) {
-                return;
+            if (imi != null) {
+                id = imi.getId();
+                settings.putSelectedInputMethod(id);
             }
-            id = imi.getId();
-            settings.putSelectedInputMethod(id);
         }
+        final var bindingController = getInputMethodBindingController(userId);
+        bindingController.setSelectedMethodId(id);
 
+        // Also re-initialize controllers.
         final var userData = getUserData(userId);
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
-
-        final var bindingController = getInputMethodBindingController(userId);
-        bindingController.setSelectedMethodId(id);
     }
 
     @GuardedBy("ImfLock.class")
@@ -3701,8 +3685,7 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     // Verify if IMMS is in the process of switching user.
-                    if (!mExperimentalConcurrentMultiUserModeEnabled
-                            && mUserSwitchHandlerTask != null) {
+                    if (!mConcurrentMultiUserModeEnabled && mUserSwitchHandlerTask != null) {
                         // There is already an on-going pending user switch task.
                         final int nextUserId = mUserSwitchHandlerTask.mToUserId;
                         if (userId == nextUserId) {
@@ -3757,7 +3740,7 @@
                     }
 
                     // Verify if caller is a background user.
-                    if (!mExperimentalConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+                    if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
                         if (ArrayUtils.contains(
                                 mUserManagerInternal.getProfileIds(mCurrentUserId, false),
                                 userId)) {
@@ -4269,9 +4252,8 @@
                 }
                 if (currentUser) {
                     // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
-                    if (mSettingsObserver != null) {
-                        mSettingsObserver.mLastEnabled = settings.getEnabledInputMethodsStr();
-                    }
+                    final var userData = getUserData(userId);
+                    userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr();
                     updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
                 }
             }
@@ -5539,7 +5521,7 @@
     @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (userId == mCurrentUserId) {
+        if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
             if (!settings.getMethodMap().containsKey(imeId)
                     || !settings.getEnabledInputMethodList()
                     .contains(settings.getMethodMap().get(imeId))) {
@@ -6110,6 +6092,8 @@
                         p.println("      inFullscreenMode=" + u.mInFullscreenMode);
                         p.println("      switchingController:");
                         u.mSwitchingController.dump(p, "        ");
+                        p.println("      mLastEnabledInputMethodsStr="
+                                + u.mLastEnabledInputMethodsStr);
                     };
             mUserDataRepository.forAllUserData(userDataDump);
 
@@ -6123,11 +6107,9 @@
             mVisibilityStateComputer.dump(pw, "  ");
             p.println("  mInFullscreenMode=" + userData.mInFullscreenMode);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
-            p.println("  mExperimentalConcurrentMultiUserModeEnabled="
-                    + mExperimentalConcurrentMultiUserModeEnabled);
+            p.println("  mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
             p.println("  ENABLE_HIDE_IME_CAPTION_BAR="
                     + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
-            p.println("  mSettingsObserver=" + mSettingsObserver);
             p.println("  mStylusIds=" + (mStylusIds != null
                     ? Arrays.toString(mStylusIds.toArray()) : ""));
 
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 48284fb..59411ad 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -174,6 +174,13 @@
                 mEnabledAccessibilitySessions = new SparseArray<>();
 
         /**
+         * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
+         */
+        @GuardedBy("ImfLock.class")
+        @NonNull
+        String mLastEnabledInputMethodsStr = "";
+
+        /**
          * Intended to be instantiated only from this file.
          */
         private UserData(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 7c1a5e1..93ef6f0 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -56,6 +56,7 @@
     private final PackageManager mPackageManager;
 
     private final ArrayList<MediaRoute2ProviderServiceProxy> mProxies = new ArrayList<>();
+    private final Runnable mScanPackagesRunnable = this::scanPackages;
     private boolean mRunning;
 
     MediaRoute2ProviderWatcher(Context context,
@@ -106,7 +107,7 @@
             mRunning = false;
 
             mContext.unregisterReceiver(mScanPackagesReceiver);
-            mHandler.removeCallbacks(this::scanPackages);
+            mHandler.removeCallbacks(mScanPackagesRunnable);
 
             // Stop all providers.
             for (int i = mProxies.size() - 1; i >= 0; i--) {
@@ -189,8 +190,8 @@
     }
 
     private void postScanPackagesIfNeeded() {
-        if (!mHandler.hasCallbacks(this::scanPackages)) {
-            mHandler.post(this::scanPackages);
+        if (!mHandler.hasCallbacks(mScanPackagesRunnable)) {
+            mHandler.post(mScanPackagesRunnable);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 173fc5c..009e9b8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4568,7 +4568,7 @@
                             PackageManagerException.INTERNAL_ERROR_SYSTEM_OVERLAY_STATIC);
                 }
             } else {
-                if ((scanFlags & SCAN_AS_VENDOR) != 0) {
+                if ((scanFlags & (SCAN_AS_VENDOR | SCAN_AS_ODM)) != 0) {
                     if (pkg.getTargetSdkVersion() < ScanPackageUtils.getVendorPartitionVersion()) {
                         Slog.w(TAG, "System overlay " + pkg.getPackageName()
                                 + " targets an SDK below the required SDK level of vendor"
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c0b8034..2e63cdb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -186,6 +186,7 @@
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.telephony.CarrierAppUtils;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -4492,8 +4493,7 @@
     void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName,
             boolean hidden) {
         final int callingUid = Binder.getCallingUid();
-        final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID
-                || callingUid == Process.SYSTEM_UID;
+        final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid);
         if (!calledFromSystemOrPhone) {
             mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
                     "setSystemAppHiddenUntilInstalled");
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index ff8abf8..924b36c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -92,6 +92,7 @@
 
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -356,7 +357,7 @@
      * If not, throws a {@link SecurityException}.
      */
     public static void enforceSystemOrPhoneCaller(String methodName, int callingUid) {
-        if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) {
+        if (!TelephonyPermissions.isSystemOrPhone(callingUid)) {
             throw new SecurityException(
                     "Cannot call " + methodName + " from UID " + callingUid);
         }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 4d07ab5..8be20b0 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1637,10 +1637,12 @@
     private boolean isSystemOrCertificateMatchingPackage(PackageInfo pi, String cert) {
         if (cert == null) {
             return pi.applicationInfo.isSystemApp();
+        } else if (Objects.equals(cert, "platform")) {
+            return mServiceInternal.isPlatformSigned(pi.packageName);
+        } else {
+            return mContext.getPackageManager().hasSigningCertificate(pi.packageName, HexEncoding.
+                    decode(cert.replace(":", "")), PackageManager.CERT_INPUT_SHA256);
         }
-
-        return mContext.getPackageManager().hasSigningCertificate(pi.packageName, HexEncoding.
-                decode(cert.replace(":", "")), PackageManager.CERT_INPUT_SHA256);
     }
 
     private static boolean doesPackageSupportRuntimePermissions(PackageInfo pkg) {
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 8f39ffb..685ab3a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -16,6 +16,8 @@
 
 package com.android.server.rollback;
 
+import static android.crashrecovery.flags.Flags.deprecateFlagsAndSettingsResets;
+
 import static com.android.server.rollback.RollbackManagerServiceImpl.sendFailure;
 
 import android.Manifest;
@@ -623,8 +625,10 @@
                 parentSession.addChildSessionId(sessionId);
             }
 
-            // Clear flags.
-            RescueParty.resetDeviceConfigForPackages(packageNames);
+            if (!deprecateFlagsAndSettingsResets()) {
+                // Clear flags.
+                RescueParty.resetDeviceConfigForPackages(packageNames);
+            }
 
             Consumer<Intent> onResult = result -> {
                 mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 2fc183d..bc29b37 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -532,7 +532,8 @@
             return false;
         }
 
-        if (Flags.keyboardCategoryEnabled() && mVibrationConfig.hasFixedKeyboardAmplitude()) {
+        if (Flags.keyboardCategoryEnabled()
+                && mVibrationConfig.isKeyboardVibrationSettingsSupported()) {
             int category = callerInfo.attrs.getCategory();
             if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
                 // Keyboard touch has a different user setting.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 484481b..8be3380 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -734,6 +734,9 @@
      */
     private boolean mOccludesParent;
 
+    /** Whether the activity have style floating */
+    private boolean mStyleFloating;
+
     /**
      * Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute
      * from the style of activity. Because we don't want {@link WindowContainer#getOrientation()}
@@ -2188,7 +2191,11 @@
                 realTheme, com.android.internal.R.styleable.Window, mUserId);
 
         if (ent != null) {
-            mOccludesParent = !ActivityInfo.isTranslucentOrFloating(ent.array)
+            final boolean styleTranslucent = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+            mStyleFloating = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsFloating, false);
+            mOccludesParent = !(styleTranslucent || mStyleFloating)
                     // This style is propagated to the main window attributes with
                     // FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
                     || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
@@ -2197,6 +2204,7 @@
             mOptOutEdgeToEdge = ent.array.getBoolean(
                     R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
         } else {
+            mStyleFloating = false;
             mStyleFillsParent = mOccludesParent = true;
             noDisplay = false;
             mOptOutEdgeToEdge = false;
@@ -3237,6 +3245,10 @@
         return occludesParent(true /* includingFinishing */);
     }
 
+    boolean isStyleFloating() {
+        return mStyleFloating;
+    }
+
     /** Returns true if this activity is not finishing, is opaque and fills the entire space of
      * this task. */
     boolean occludesParent() {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d45ed12..14e256f 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1335,12 +1335,16 @@
                 }
                 // If there is only one adaptor, attach the windowless window to top activity,
                 // because fixed rotation only applies on activity.
-                // Note that embedded activity won't use fixed rotation.
-                final Configuration openConfig = mAdaptors.length == 1
+                // Note that embedded activity won't use fixed rotation. Also, there is only one
+                // animation target for closing task.
+                final boolean chooseActivity = mAdaptors.length == 1
+                        && (switchType == ACTIVITY_SWITCH || mainActivity.mDisplayContent
+                                .isFixedRotationLaunchingApp(mainActivity));
+                final Configuration openConfig = chooseActivity
                         ? mainActivity.getConfiguration() : openTask.getConfiguration();
                 mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
                         .addWindowlessStartingSurface(openTask, mainActivity,
-                                mAdaptors.length == 1 ? mainActivity.getSurfaceControl()
+                                chooseActivity ? mainActivity.getSurfaceControl()
                                         : mRemoteAnimationTarget.leash, snapshot, openConfig,
                             new IWindowlessStartingSurfaceCallback.Stub() {
                             // Once the starting surface has been created in shell, it will call
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 857e03d..475a504 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -16,9 +16,19 @@
 
 package com.android.server.wm;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.feature.flags.FeatureFlags;
+import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
 import android.util.ArrayMap;
 import android.util.Pair;
 
@@ -28,6 +38,7 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -43,16 +54,16 @@
     @NonNull
     private final WindowManagerGlobalLock mWmLock;
     @NonNull
-    private final int[] mOpenDeviceStates;
+    private final List<Integer> mOpenDeviceStates;
     @NonNull
-    private final int[] mHalfFoldedDeviceStates;
+    private final List<Integer> mHalfFoldedDeviceStates;
     @NonNull
-    private final int[] mFoldedDeviceStates;
+    private final List<Integer> mFoldedDeviceStates;
     @NonNull
-    private final int[] mRearDisplayDeviceStates;
-    private final int mConcurrentDisplayDeviceState;
+    private final List<Integer> mRearDisplayDeviceStates;
+    private final List<Integer> mConcurrentDisplayDeviceStates;
     @NonNull
-    private final int[] mReverseRotationAroundZAxisStates;
+    private final List<Integer> mReverseRotationAroundZAxisStates;
     @GuardedBy("mWmLock")
     @NonNull
     @VisibleForTesting
@@ -76,18 +87,55 @@
     DeviceStateController(@NonNull Context context, @NonNull WindowManagerGlobalLock wmLock) {
         mWmLock = wmLock;
 
-        mOpenDeviceStates = context.getResources()
-                .getIntArray(R.array.config_openDeviceStates);
-        mHalfFoldedDeviceStates = context.getResources()
-                .getIntArray(R.array.config_halfFoldedDeviceStates);
-        mFoldedDeviceStates = context.getResources()
-                .getIntArray(R.array.config_foldedDeviceStates);
-        mRearDisplayDeviceStates = context.getResources()
-                .getIntArray(R.array.config_rearDisplayDeviceStates);
-        mConcurrentDisplayDeviceState = context.getResources()
-                .getInteger(R.integer.config_deviceStateConcurrentRearDisplay);
-        mReverseRotationAroundZAxisStates = context.getResources()
-                .getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
+        final FeatureFlags deviceStateManagerFlags = new FeatureFlagsImpl();
+        if (deviceStateManagerFlags.deviceStatePropertyMigration()) {
+            mOpenDeviceStates = new ArrayList<>();
+            mHalfFoldedDeviceStates = new ArrayList<>();
+            mFoldedDeviceStates = new ArrayList<>();
+            mRearDisplayDeviceStates = new ArrayList<>();
+            mConcurrentDisplayDeviceStates = new ArrayList<>();
+
+            final DeviceStateManager deviceStateManager =
+                    context.getSystemService(DeviceStateManager.class);
+            final List<android.hardware.devicestate.DeviceState> deviceStates =
+                    deviceStateManager.getSupportedDeviceStates();
+
+            for (int i = 0; i < deviceStates.size(); i++) {
+                final android.hardware.devicestate.DeviceState state = deviceStates.get(i);
+                if (state.hasProperty(
+                        PROPERTY_FEATURE_REAR_DISPLAY)) {
+                    mRearDisplayDeviceStates.add(state.getIdentifier());
+                } else if (state.hasProperty(
+                        PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT)) {
+                    mConcurrentDisplayDeviceStates.add(state.getIdentifier());
+                } else if (state.hasProperty(
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)) {
+                    mFoldedDeviceStates.add(state.getIdentifier());
+                } else if (state.hasProperty(
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)) {
+                    if (state.hasProperty(
+                            PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN)) {
+                        mHalfFoldedDeviceStates.add(state.getIdentifier());
+                    } else {
+                        mOpenDeviceStates.add(state.getIdentifier());
+                    }
+                }
+            }
+        } else {
+            mOpenDeviceStates = copyIntArrayToList(context.getResources()
+                    .getIntArray(R.array.config_openDeviceStates));
+            mHalfFoldedDeviceStates = copyIntArrayToList(context.getResources()
+                    .getIntArray(R.array.config_halfFoldedDeviceStates));
+            mFoldedDeviceStates = copyIntArrayToList(context.getResources()
+                    .getIntArray(R.array.config_foldedDeviceStates));
+            mRearDisplayDeviceStates = copyIntArrayToList(context.getResources()
+                    .getIntArray(R.array.config_rearDisplayDeviceStates));
+            mConcurrentDisplayDeviceStates = new ArrayList<>(List.of(context.getResources()
+                    .getInteger(R.integer.config_deviceStateConcurrentRearDisplay)));
+        }
+
+        mReverseRotationAroundZAxisStates = copyIntArrayToList(context.getResources().getIntArray(
+                R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis));
         mMatchBuiltInDisplayOrientationToDefaultDisplay = context.getResources()
                 .getBoolean(R.bool
                         .config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay);
@@ -145,7 +193,6 @@
      */
     public void onDeviceStateReceivedByDisplayManager(int state) {
         mCurrentState = state;
-
         final DeviceState deviceState;
         if (ArrayUtils.contains(mHalfFoldedDeviceStates, state)) {
             deviceState = DeviceState.HALF_FOLDED;
@@ -155,9 +202,10 @@
             deviceState = DeviceState.REAR;
         } else if (ArrayUtils.contains(mOpenDeviceStates, state)) {
             deviceState = DeviceState.OPEN;
-        } else if (state == mConcurrentDisplayDeviceState) {
+        } else if (ArrayUtils.contains(mConcurrentDisplayDeviceStates, state)) {
             deviceState = DeviceState.CONCURRENT;
         } else {
+
             deviceState = DeviceState.UNKNOWN;
         }
 
@@ -190,4 +238,16 @@
         }
         return entries;
     }
+
+    @NonNull
+    private List<Integer> copyIntArrayToList(@Nullable int[] values) {
+        if (values == null) {
+            return Collections.emptyList();
+        }
+        final List<Integer> valueList = new ArrayList<>();
+        for (int i = 0; i < values.length; i++) {
+            valueList.add(values[i]);
+        }
+        return valueList;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7206b36..33a649b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3411,6 +3411,7 @@
         info.isVisibleRequested = isVisibleRequested();
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
+        info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
         appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 35a7702..f839ed6 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1694,7 +1694,7 @@
         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
         // needs to be updated for STATE_ABORT.
         commitVisibleActivities(transaction);
-        commitVisibleWallpapers();
+        commitVisibleWallpapers(transaction);
 
         if (mTransactionCompletedListeners != null) {
             for (int i = 0; i < mTransactionCompletedListeners.size(); i++) {
@@ -2128,7 +2128,7 @@
     /**
      * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
      */
-    private void commitVisibleWallpapers() {
+    private void commitVisibleWallpapers(SurfaceControl.Transaction t) {
         boolean showWallpaper = shouldWallpaperBeVisible();
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
@@ -2136,6 +2136,14 @@
                 if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
                     wallpaper.commitVisibility(showWallpaper);
                 }
+                if (showWallpaper && Flags.ensureWallpaperInTransitions()
+                        && wallpaper.isVisibleRequested()
+                        && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) {
+                    // If on a rotation leash, we need to explicitly show the wallpaper surface
+                    // because shell only gets the leash and we don't allow non-transition logic
+                    // to touch the surfaces until the transition is over.
+                    t.show(wallpaper.getSurfaceControl());
+                }
             }
         }
     }
@@ -2837,6 +2845,13 @@
                     // Use parent rotation because shell doesn't know the surface is rotated.
                     endRotation = parent.getWindowConfiguration().getRotation();
                 }
+            } else if (isWallpaper(target) && Flags.ensureWallpaperInTransitions()
+                    && target.getRelativeDisplayRotation() != 0
+                    && !target.mTransitionController.useShellTransitionsRotation()) {
+                // If the wallpaper is "fixed-rotated", shell is unaware of this, so use the
+                // "as-if-not-rotating" bounds and rotation
+                change.setEndAbsBounds(parent.getBounds());
+                endRotation = parent.getWindowConfiguration().getRotation();
             } else {
                 change.setEndAbsBounds(bounds);
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index a42cb09..2ea1cf8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -44,6 +44,7 @@
 import android.view.InputChannel;
 import android.view.MagnificationSpec;
 import android.view.RemoteAnimationTarget;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.WindowInfo;
@@ -817,6 +818,16 @@
     public abstract Context getTopFocusedDisplayUiContext();
 
     /**
+     * Sets the rotation of a non-default display.
+     *
+     * @param displayId The id of the display
+     * @param rotation The new rotation value.
+     * @param caller The requester of the rotation change, used for bookkeeping.
+     */
+    public abstract void setNonDefaultDisplayRotation(int displayId, @Surface.Rotation int rotation,
+            @NonNull String caller);
+
+    /**
      * Sets whether the relevant display content can host the relevant home activity and wallpaper.
      *
      * @param displayUniqueId The unique ID of the display. Note that the display may not yet be
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 57b8040..0c1ec504 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8362,6 +8362,26 @@
         }
 
         @Override
+        public void setNonDefaultDisplayRotation(int displayId, @Surface.Rotation int rotation,
+                @NonNull String caller) {
+            if (displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY) {
+                Slog.w(TAG, "Cannot set rotation for display with id: " + displayId);
+                return;
+            }
+            synchronized (mGlobalLock) {
+                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+                if (displayContent == null) {
+                    Slog.w(TAG, "Cannot set rotation for display " + displayId
+                            + " due to missing DisplayContent");
+                    return;
+                }
+                displayContent.getDisplayRotation().setUserRotation(
+                        displayContent.getDisplayRotation().getUserRotationMode(), rotation,
+                        caller);
+            }
+        }
+
+        @Override
         public void setHomeSupportedOnDisplay(String displayUniqueId, int displayType,
                 boolean supported) {
             final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fec1175..de73e6c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,6 +28,7 @@
 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.util.SequenceUtils.getNextSeq;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
@@ -96,7 +97,6 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
-import static android.util.SequenceUtils.getNextSeq;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
@@ -249,8 +249,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.KeyInterceptionInfo;
-import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
@@ -1976,8 +1976,8 @@
      */
     boolean isReadyForDisplay() {
         final boolean parentAndClientVisible = !isParentWindowHidden()
-                && mViewVisibility == View.VISIBLE && mToken.isVisible();
-        return mHasSurface && isVisibleByPolicy() && !mDestroying
+                && mViewVisibility == View.VISIBLE;
+        return mHasSurface && isVisibleByPolicy() && !mDestroying && mToken.isVisible()
                 && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7a710dc..7649a4e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -193,7 +193,7 @@
         "android.hardware.thermal-V2-ndk",
         "android.hardware.tv.input@1.0",
         "android.hardware.tv.input-V2-ndk",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V2-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 4be21d8..2804a10 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -16,24 +16,21 @@
 
 #define LOG_TAG "VibratorController"
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/IVibrator.h>
-
 #include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+#include <vibratorservice/VibratorHalController.h>
+
 #include "android_runtime/AndroidRuntime.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
 
-#include <utils/Log.h>
-#include <utils/misc.h>
-
-#include <vibratorservice/VibratorHalController.h>
-
-#include "com_android_server_vibrator_VibratorManagerService.h"
-
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_3 = android::hardware::vibrator::V1_3;
-namespace aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -67,29 +64,29 @@
 } sRampClassInfo;
 
 static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
-              static_cast<uint8_t>(aidl::EffectStrength::LIGHT));
+              static_cast<uint8_t>(Aidl::EffectStrength::LIGHT));
 static_assert(static_cast<uint8_t>(V1_0::EffectStrength::MEDIUM) ==
-              static_cast<uint8_t>(aidl::EffectStrength::MEDIUM));
+              static_cast<uint8_t>(Aidl::EffectStrength::MEDIUM));
 static_assert(static_cast<uint8_t>(V1_0::EffectStrength::STRONG) ==
-              static_cast<uint8_t>(aidl::EffectStrength::STRONG));
+              static_cast<uint8_t>(Aidl::EffectStrength::STRONG));
 
 static_assert(static_cast<uint8_t>(V1_3::Effect::CLICK) ==
-              static_cast<uint8_t>(aidl::Effect::CLICK));
+              static_cast<uint8_t>(Aidl::Effect::CLICK));
 static_assert(static_cast<uint8_t>(V1_3::Effect::DOUBLE_CLICK) ==
-              static_cast<uint8_t>(aidl::Effect::DOUBLE_CLICK));
-static_assert(static_cast<uint8_t>(V1_3::Effect::TICK) == static_cast<uint8_t>(aidl::Effect::TICK));
-static_assert(static_cast<uint8_t>(V1_3::Effect::THUD) == static_cast<uint8_t>(aidl::Effect::THUD));
-static_assert(static_cast<uint8_t>(V1_3::Effect::POP) == static_cast<uint8_t>(aidl::Effect::POP));
+              static_cast<uint8_t>(Aidl::Effect::DOUBLE_CLICK));
+static_assert(static_cast<uint8_t>(V1_3::Effect::TICK) == static_cast<uint8_t>(Aidl::Effect::TICK));
+static_assert(static_cast<uint8_t>(V1_3::Effect::THUD) == static_cast<uint8_t>(Aidl::Effect::THUD));
+static_assert(static_cast<uint8_t>(V1_3::Effect::POP) == static_cast<uint8_t>(Aidl::Effect::POP));
 static_assert(static_cast<uint8_t>(V1_3::Effect::HEAVY_CLICK) ==
-              static_cast<uint8_t>(aidl::Effect::HEAVY_CLICK));
+              static_cast<uint8_t>(Aidl::Effect::HEAVY_CLICK));
 static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_1) ==
-              static_cast<uint8_t>(aidl::Effect::RINGTONE_1));
+              static_cast<uint8_t>(Aidl::Effect::RINGTONE_1));
 static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_2) ==
-              static_cast<uint8_t>(aidl::Effect::RINGTONE_2));
+              static_cast<uint8_t>(Aidl::Effect::RINGTONE_2));
 static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_15) ==
-              static_cast<uint8_t>(aidl::Effect::RINGTONE_15));
+              static_cast<uint8_t>(Aidl::Effect::RINGTONE_15));
 static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) ==
-              static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
+              static_cast<uint8_t>(Aidl::Effect::TEXTURE_TICK));
 
 static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
     vibrator::ManagerHalController* manager =
@@ -155,15 +152,15 @@
     std::atomic<int64_t> mCallbackId;
 };
 
-static aidl::BrakingPwle brakingPwle(aidl::Braking braking, int32_t duration) {
-    aidl::BrakingPwle pwle;
+static Aidl::BrakingPwle brakingPwle(Aidl::Braking braking, int32_t duration) {
+    Aidl::BrakingPwle pwle;
     pwle.braking = braking;
     pwle.duration = duration;
     return pwle;
 }
 
-static aidl::ActivePwle activePwleFromJavaPrimitive(JNIEnv* env, jobject ramp) {
-    aidl::ActivePwle pwle;
+static Aidl::ActivePwle activePwleFromJavaPrimitive(JNIEnv* env, jobject ramp) {
+    Aidl::ActivePwle pwle;
     pwle.startAmplitude =
             static_cast<float>(env->GetFloatField(ramp, sRampClassInfo.startAmplitude));
     pwle.endAmplitude = static_cast<float>(env->GetFloatField(ramp, sRampClassInfo.endAmplitude));
@@ -175,20 +172,20 @@
 }
 
 /* Return true if braking is not NONE and the active PWLE starts and ends with zero amplitude. */
-static bool shouldBeReplacedWithBraking(aidl::ActivePwle activePwle, aidl::Braking braking) {
-    return (braking != aidl::Braking::NONE) && (activePwle.startAmplitude == 0) &&
+static bool shouldBeReplacedWithBraking(Aidl::ActivePwle activePwle, Aidl::Braking braking) {
+    return (braking != Aidl::Braking::NONE) && (activePwle.startAmplitude == 0) &&
             (activePwle.endAmplitude == 0);
 }
 
 /* Return true if braking is not NONE and the active PWLE only ends with zero amplitude. */
-static bool shouldAddLastBraking(aidl::ActivePwle lastActivePwle, aidl::Braking braking) {
-    return (braking != aidl::Braking::NONE) && (lastActivePwle.startAmplitude > 0) &&
+static bool shouldAddLastBraking(Aidl::ActivePwle lastActivePwle, Aidl::Braking braking) {
+    return (braking != Aidl::Braking::NONE) && (lastActivePwle.startAmplitude > 0) &&
             (lastActivePwle.endAmplitude == 0);
 }
 
-static aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primitive) {
-    aidl::CompositeEffect effect;
-    effect.primitive = static_cast<aidl::CompositePrimitive>(
+static Aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primitive) {
+    Aidl::CompositeEffect effect;
+    effect.primitive = static_cast<Aidl::CompositePrimitive>(
             env->GetIntField(primitive, sPrimitiveClassInfo.id));
     effect.scale = static_cast<float>(env->GetFloatField(primitive, sPrimitiveClassInfo.scale));
     effect.delayMs = static_cast<int32_t>(env->GetIntField(primitive, sPrimitiveClassInfo.delay));
@@ -282,8 +279,8 @@
         ALOGE("vibratorPerformEffect failed because native wrapper was not initialized");
         return -1;
     }
-    aidl::Effect effectType = static_cast<aidl::Effect>(effect);
-    aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength);
+    Aidl::Effect effectType = static_cast<Aidl::Effect>(effect);
+    Aidl::EffectStrength effectStrength = static_cast<Aidl::EffectStrength>(strength);
     auto callback = wrapper->createCallback(vibrationId);
     auto performEffectFn = [effectType, effectStrength, &callback](vibrator::HalWrapper* hal) {
         return hal->performEffect(effectType, effectStrength, callback);
@@ -300,7 +297,7 @@
         return -1;
     }
     size_t size = env->GetArrayLength(composition);
-    std::vector<aidl::CompositeEffect> effects;
+    std::vector<Aidl::CompositeEffect> effects;
     for (size_t i = 0; i < size; i++) {
         jobject element = env->GetObjectArrayElement(composition, i);
         effects.push_back(effectFromJavaPrimitive(env, element));
@@ -321,13 +318,13 @@
         ALOGE("vibratorPerformPwleEffect failed because native wrapper was not initialized");
         return -1;
     }
-    aidl::Braking braking = static_cast<aidl::Braking>(brakingId);
+    Aidl::Braking braking = static_cast<Aidl::Braking>(brakingId);
     size_t size = env->GetArrayLength(waveform);
-    std::vector<aidl::PrimitivePwle> primitives;
+    std::vector<Aidl::PrimitivePwle> primitives;
     std::chrono::milliseconds totalDuration(0);
     for (size_t i = 0; i < size; i++) {
         jobject element = env->GetObjectArrayElement(waveform, i);
-        aidl::ActivePwle activePwle = activePwleFromJavaPrimitive(env, element);
+        Aidl::ActivePwle activePwle = activePwleFromJavaPrimitive(env, element);
         if ((i > 0) && shouldBeReplacedWithBraking(activePwle, braking)) {
             primitives.push_back(brakingPwle(braking, activePwle.duration));
         } else {
@@ -356,8 +353,8 @@
         return;
     }
     auto alwaysOnEnableFn = [id, effect, strength](vibrator::HalWrapper* hal) {
-        return hal->alwaysOnEnable(static_cast<int32_t>(id), static_cast<aidl::Effect>(effect),
-                                   static_cast<aidl::EffectStrength>(strength));
+        return hal->alwaysOnEnable(static_cast<int32_t>(id), static_cast<Aidl::Effect>(effect),
+                                   static_cast<Aidl::EffectStrength>(strength));
     };
     wrapper->halCall<void>(alwaysOnEnableFn, "alwaysOnEnable");
 }
@@ -389,7 +386,7 @@
                               static_cast<jlong>(info.capabilities.value()));
     }
     if (info.supportedEffects.isOk()) {
-        std::vector<aidl::Effect> effects = info.supportedEffects.value();
+        std::vector<Aidl::Effect> effects = info.supportedEffects.value();
         jintArray supportedEffects = env->NewIntArray(effects.size());
         env->SetIntArrayRegion(supportedEffects, 0, effects.size(),
                                reinterpret_cast<jint*>(effects.data()));
@@ -397,7 +394,7 @@
                               sVibratorInfoBuilderClassInfo.setSupportedEffects, supportedEffects);
     }
     if (info.supportedBraking.isOk()) {
-        std::vector<aidl::Braking> braking = info.supportedBraking.value();
+        std::vector<Aidl::Braking> braking = info.supportedBraking.value();
         jintArray supportedBraking = env->NewIntArray(braking.size());
         env->SetIntArrayRegion(supportedBraking, 0, braking.size(),
                                reinterpret_cast<jint*>(braking.data()));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 215cf2c..791d030 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2134,14 +2134,20 @@
             }
             t.traceEnd();
 
-            t.traceBegin("StartVpnManagerService");
-            try {
-                vpnManager = VpnManagerService.create(context);
-                ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager);
-            } catch (Throwable e) {
-                reportWtf("starting VPN Manager Service", e);
+            if (!isWatch || !android.server.Flags.allowRemovingVpnService()) {
+                t.traceBegin("StartVpnManagerService");
+                try {
+                    vpnManager = VpnManagerService.create(context);
+                    ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager);
+                } catch (Throwable e) {
+                    reportWtf("starting VPN Manager Service", e);
+                }
+                t.traceEnd();
+            } else {
+                // VPN management currently does not work in Wear, so skip starting the
+                // VPN manager SystemService.
+                Slog.i(TAG, "Not starting VpnManagerService");
             }
-            t.traceEnd();
 
             t.traceBegin("StartVcnManagementService");
             try {
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index e8aa68c..29f3871 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -21,4 +21,11 @@
      namespace: "wear_frameworks"
      description: "Remove WearableSensingManagerService on Wear"
      bug: "340929916"
+}
+
+flag {
+     name: "allow_removing_vpn_service"
+     namespace: "wear_frameworks"
+     description: "Allow removing VpnManagerService"
+     bug: "340928692"
 }
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 78dbc60..0b7438c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2687,7 +2687,7 @@
                 runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += deviceId
             }
 
-            if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) {
+            if (permission.hasGids && (wasPermissionGranted != isPermissionGranted)) {
                 gidsChangedUids += uid
             }
         }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 80eab11..17d9ef9 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -233,7 +233,7 @@
                         Process.THREAD_PRIORITY_FOREGROUND,
                         true /* allowIo */);
         mInputMethodManagerService = new InputMethodManagerService(mContext,
-                InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext),
+                InputMethodManagerService.shouldEnableConcurrentMultiUserMode(mContext),
                 mServiceThread, mIoThread,
                 unusedUserId -> mMockInputMethodBindingController);
         spyOn(mInputMethodManagerService);
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 3377899..f2acbc3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -27,7 +27,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.RescueParty.DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN;
 import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
-import static com.android.server.RescueParty.RESCUE_LEVEL_FACTORY_RESET;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -36,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -47,6 +45,9 @@
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+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.provider.DeviceConfig;
 import android.provider.Settings;
@@ -61,6 +62,9 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -77,10 +81,14 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
+
 /**
  * Test RescueParty.
  */
+@RunWith(Parameterized.class)
 public class RescuePartyTest {
+    @Rule
+    public SetFlagsRule mSetFlagsRule;
     private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
     private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
     private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
@@ -103,9 +111,6 @@
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     private MockitoSession mSession;
     private HashMap<String, String> mSystemSettingsMap;
     private HashMap<String, String> mCrashRecoveryPropertiesMap;
@@ -129,6 +134,17 @@
     @Captor
     private ArgumentCaptor<List<String>> mPackageListCaptor;
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getFlags() {
+        return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_RECOVERABILITY_DETECTION,
+                Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS);
+    }
+
+    public RescuePartyTest(FlagsParameterization flags) {
+        mSetFlagsRule = new SetFlagsRule(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         mSession =
@@ -234,10 +250,10 @@
     }
 
     @Test
-    public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
+    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
+            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
+    public void testBootLoop() {
         // this is old test where the flag needs to be disabled
-        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -264,10 +280,22 @@
         noteBoot(5);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
+    @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testBootLoopNoFlags() {
+        // this is old test where the flag needs to be disabled
+        noteBoot(1);
+        assertTrue(RescueParty.isRebootPropertySet());
+
+        setCrashRecoveryPropAttemptingReboot(false);
+        noteBoot(2);
+        assertTrue(RescueParty.isFactoryResetPropertySet());
+    }
 
     @Test
-    public void testBootLoopDetectionWithExecutionForAllRescueLevelsRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testBootLoopRecoverability() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -281,12 +309,14 @@
 
         final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
 
+
         noteBoot(1);
 
         noteBoot(2);
         assertTrue(RescueParty.isRebootPropertySet());
 
         noteBoot(3);
+
         verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
 
         noteBoot(4);
@@ -301,10 +331,10 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
+    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
+            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
+    public void testPersistentAppCrash() {
         // this is old test where the flag needs to be disabled
-        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-
         noteAppCrash(1, true);
         noteAppCrash(2, true);
         noteAppCrash(3, true);
@@ -318,8 +348,21 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevelsRecoverability() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testPersistentAppCrashNoFlags() {
+        // this is old test where the flag needs to be disabled
+        noteAppCrash(1, true);
+        assertTrue(RescueParty.isRebootPropertySet());
+
+        setCrashRecoveryPropAttemptingReboot(false);
+        noteAppCrash(2, true);
+        assertTrue(RescueParty.isFactoryResetPropertySet());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testPersistentAppCrashRecoverability() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -357,10 +400,10 @@
     }
 
     @Test
-    public void testNonPersistentAppDoesntDoAnything() {
+    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
+            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
+    public void testNonPersistentApp() {
         // this is old test where the flag needs to be disabled
-        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-
         noteAppCrash(1, false);
         noteAppCrash(2, false);
         noteAppCrash(3, false);
@@ -371,8 +414,9 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testNonPersistentAppOnlyPerformsFlagResetsRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -408,60 +452,6 @@
     }
 
     @Test
-    public void testNonPersistentAppCrashDetectionWithScopedResets() {
-        // this is old test where the flag needs to be disabled
-        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-
-        // Record DeviceConfig accesses
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
-
-        // Fake DeviceConfig value changes
-        monitorCallback.onNamespaceUpdate(NAMESPACE1);
-        verify(mMockPackageWatchdog).startObservingHealth(observer,
-                Arrays.asList(CALLING_PACKAGE1), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
-        monitorCallback.onNamespaceUpdate(NAMESPACE2);
-        verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer),
-                mPackageListCaptor.capture(),
-                eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS));
-        monitorCallback.onNamespaceUpdate(NAMESPACE3);
-        verify(mMockPackageWatchdog).startObservingHealth(observer,
-                Arrays.asList(CALLING_PACKAGE2), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
-        assertTrue(mPackageListCaptor.getValue().containsAll(
-                Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2)));
-        // Perform and verify scoped resets
-        final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-        final String[] expectedAllResetNamespaces =
-                new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
-        HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-        observer.execute(new VersionedPackage(
-                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-
-        observer.execute(new VersionedPackage(
-                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
-
-        observer.execute(new VersionedPackage(
-                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
-
-        observer.execute(new VersionedPackage(
-                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
-        assertFalse(RescueParty.isRebootPropertySet());
-
-        observer.execute(new VersionedPackage(
-                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
     public void testIsRecoveryTriggeredReboot() {
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
@@ -474,19 +464,6 @@
     }
 
     @Test
-    public void testIsRecoveryTriggeredRebootRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteBoot(i + 1);
-        }
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteBoot(RESCUE_LEVEL_FACTORY_RESET + 1);
-        assertTrue(RescueParty.isRecoveryTriggeredReboot());
-        assertTrue(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
     public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() {
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
@@ -505,25 +482,6 @@
     }
 
     @Test
-    public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompletedRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteBoot(i + 1);
-        }
-        int mitigationCount = RESCUE_LEVEL_FACTORY_RESET + 1;
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-        noteBoot(mitigationCount++);
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-        noteBoot(mitigationCount++);
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-        noteBoot(mitigationCount++);
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteBoot(mitigationCount + 1);
-        assertTrue(RescueParty.isRecoveryTriggeredReboot());
-        assertTrue(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
     public void testThrottlingOnBootFailures() {
         setCrashRecoveryPropAttemptingReboot(false);
         long now = System.currentTimeMillis();
@@ -537,20 +495,6 @@
     }
 
     @Test
-    public void testThrottlingOnBootFailuresRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        setCrashRecoveryPropAttemptingReboot(false);
-        long now = System.currentTimeMillis();
-        long beforeTimeout = now - TimeUnit.MINUTES.toMillis(
-                DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1);
-        setCrashRecoveryPropLastFactoryReset(beforeTimeout);
-        for (int i = 1; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteBoot(i);
-        }
-        assertFalse(RescueParty.isRecoveryTriggeredReboot());
-    }
-
-    @Test
     public void testThrottlingOnAppCrash() {
         setCrashRecoveryPropAttemptingReboot(false);
         long now = System.currentTimeMillis();
@@ -564,20 +508,6 @@
     }
 
     @Test
-    public void testThrottlingOnAppCrashRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        setCrashRecoveryPropAttemptingReboot(false);
-        long now = System.currentTimeMillis();
-        long beforeTimeout = now - TimeUnit.MINUTES.toMillis(
-                DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1);
-        setCrashRecoveryPropLastFactoryReset(beforeTimeout);
-        for (int i = 0; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteAppCrash(i + 1, true);
-        }
-        assertFalse(RescueParty.isRecoveryTriggeredReboot());
-    }
-
-    @Test
     public void testNotThrottlingAfterTimeoutOnBootFailures() {
         setCrashRecoveryPropAttemptingReboot(false);
         long now = System.currentTimeMillis();
@@ -591,20 +521,6 @@
     }
 
     @Test
-    public void testNotThrottlingAfterTimeoutOnBootFailuresRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        setCrashRecoveryPropAttemptingReboot(false);
-        long now = System.currentTimeMillis();
-        long afterTimeout = now - TimeUnit.MINUTES.toMillis(
-                DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1);
-        setCrashRecoveryPropLastFactoryReset(afterTimeout);
-        for (int i = 1; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteBoot(i);
-        }
-        assertTrue(RescueParty.isRecoveryTriggeredReboot());
-    }
-
-    @Test
     public void testNotThrottlingAfterTimeoutOnAppCrash() {
         setCrashRecoveryPropAttemptingReboot(false);
         long now = System.currentTimeMillis();
@@ -618,20 +534,7 @@
     }
 
     @Test
-    public void testNotThrottlingAfterTimeoutOnAppCrashRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        setCrashRecoveryPropAttemptingReboot(false);
-        long now = System.currentTimeMillis();
-        long afterTimeout = now - TimeUnit.MINUTES.toMillis(
-                DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1);
-        setCrashRecoveryPropLastFactoryReset(afterTimeout);
-        for (int i = 0; i <= RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteAppCrash(i + 1, true);
-        }
-        assertTrue(RescueParty.isRecoveryTriggeredReboot());
-    }
-
-    @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testNativeRescuePartyResets() {
         doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
         doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
@@ -647,7 +550,6 @@
 
     @Test
     public void testExplicitlyEnablingAndDisablingRescue() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
@@ -660,7 +562,6 @@
 
     @Test
     public void testDisablingRescueByDeviceConfigFlag() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
 
@@ -686,24 +587,10 @@
     }
 
     @Test
-    public void testDisablingFactoryResetByDeviceConfigFlagRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-        SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
-
-        for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) {
-            noteBoot(i + 1);
-        }
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-
-        // Restore the property value initialized in SetUp()
-        SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
-    }
-
-    @Test
+    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
+            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
     public void testHealthCheckLevels() {
         // this is old test where the flag needs to be disabled
-        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
-
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
         // Ensure that no action is taken for cases where the failure reason is unknown
@@ -729,8 +616,9 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
     public void testHealthCheckLevelsRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
         // Ensure that no action is taken for cases where the failure reason is unknown
@@ -767,11 +655,31 @@
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 7),
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
     }
-
     @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testHealthCheckLevelsNoFlags() {
+        // this is old test where the flag needs to be disabled
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+        // Ensure that no action is taken for cases where the failure reason is unknown
+        assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+
+        // Ensure the correct user impact is returned for each mitigation count.
+        assertEquals(observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+
+        assertEquals(observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+    }
+    @Test
+    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
+            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
     public void testBootLoopLevels() {
         // this is old test where the flag needs to be disabled
-        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
 
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
@@ -784,8 +692,9 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
     public void testBootLoopLevelsRecoverabilityDetection() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
         assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
@@ -797,6 +706,16 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testBootLoopLevelsNoFlags() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
@@ -827,6 +746,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testResetDeviceConfigForPackagesOnlyPresetMap() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
@@ -835,7 +755,7 @@
 
         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
                 + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
-                + NAMESPACE3 +  ":" + CALLING_PACKAGE1;
+                + NAMESPACE3 + ":" + CALLING_PACKAGE1;
         doReturn(presetMapping).when(() -> DeviceConfig.getString(
                 eq(RescueParty.NAMESPACE_CONFIGURATION),
                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
@@ -848,6 +768,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testResetDeviceConfigForPackagesBothMaps() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
@@ -884,6 +805,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
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 ecd799f..6ec888c 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
@@ -406,8 +406,6 @@
         mContextInjector.getValue().accept(opContext);
 
         verify(mHal).onContextChanged(same(opContext));
-        verify(mHal, times(2)).setIgnoreDisplayTouches(
-                opContext.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
 
         client.stopHalOperation();
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 21364b8..87b52e6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2068,7 +2068,6 @@
         assertThat(mPowerManager.isInteractive()).isTrue();
     }
 
-
     @Test
     public void handleStandby_fromNonActiveSource_previousActiveSourceNotSet_Standby() {
         HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
@@ -2091,6 +2090,35 @@
                 .isFalse();
     }
 
+    @Test
+    public void handleReportPhysicalAddress_DeviceDiscoveryActionInProgress_noNewDeviceAction() {
+        mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPhysicalAddressFromPlayback1 =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_PLAYBACK_1, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        HdmiCecMessage reportPhysicalAddressFromPlayback2 =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_PLAYBACK_2, 0x2000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        HdmiCecMessage giveOsdName = HdmiCecMessageBuilder.buildGiveOsdNameCommand(
+                ADDR_TV, ADDR_PLAYBACK_2);
+        // Skip state waiting for <Report Physical Address> for DeviceDiscoveryAction s.t. message
+        // can be dispatched to local device TV.
+        mNativeWrapper.onCecMessage(reportPhysicalAddressFromPlayback1);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(reportPhysicalAddressFromPlayback2);
+        mTestLooper.dispatchAll();
+
+        // NewDeviceAction did not start and <Give OSD Name> was not sent.
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveOsdName);
+    }
+
     protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
         MockTvDevice(HdmiControlService service) {
             super(service);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index f5ab95c..f7340ab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -18,8 +18,10 @@
 
 import static android.app.AutomaticZenRule.TYPE_BEDTIME;
 import static android.app.Flags.FLAG_MODES_UI;
+import static android.app.Flags.modesUi;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.Condition.SOURCE_UNKNOWN;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
@@ -36,10 +38,18 @@
 import static junit.framework.TestCase.assertNull;
 import static junit.framework.TestCase.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.app.AutomaticZenRule;
 import android.app.Flags;
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Parcel;
 import android.platform.test.annotations.DisableFlags;
@@ -66,6 +76,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedInputStream;
@@ -102,6 +114,9 @@
     private final boolean ENABLED = true;
     private final int CREATION_TIME = 123;
 
+    @Mock
+    PackageManager mPm;
+
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
             SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
@@ -119,6 +134,8 @@
     @Before
     public final void setUp() {
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+        MockitoAnnotations.initMocks(this);
+        mContext.setMockPackageManager(mPm);
     }
 
     @Test
@@ -967,6 +984,85 @@
         assertThat(fromXml.zenPolicy).isEqualTo(config.getZenPolicy());
     }
 
+    @Test
+    public void testGetDescription_off() {
+        ZenModeConfig config = new ZenModeConfig();
+        if (!modesUi()) {
+            config.manualRule = new ZenModeConfig.ZenRule();
+        }
+        config.manualRule.pkg = "android";
+        assertThat(ZenModeConfig.getDescription(mContext, true, config, false)).isNull();
+    }
+
+    @Test
+    public void testGetDescription_on_manual_endTime() {
+        ZenModeConfig config = new ZenModeConfig();
+        if (!modesUi()) {
+            config.manualRule = new ZenModeConfig.ZenRule();
+        }
+        config.manualRule.conditionId = ZenModeConfig.toCountdownConditionId(
+                System.currentTimeMillis() + 10000, false);
+        config.manualRule.pkg = "android";
+        config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_TRUE, SOURCE_UNKNOWN);
+        assertThat(ZenModeConfig.getDescription(mContext, true, config, false))
+                .startsWith("Until");
+    }
+
+    @Test
+    public void getSoundSummary_on_manual_noEnd() {
+        ZenModeConfig config = new ZenModeConfig();
+        if (!modesUi()) {
+            config.manualRule = new ZenModeConfig.ZenRule();
+        }
+        config.manualRule.conditionId = Uri.EMPTY;
+        config.manualRule.pkg = "android";
+        config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_TRUE, SOURCE_UNKNOWN);
+        assertThat(ZenModeConfig.getDescription(mContext, true, config, false)).isNull();
+    }
+
+    @Test
+    public void getSoundSummary_on_manual_enabler() throws Exception {
+        ApplicationInfo ai = mock(ApplicationInfo.class);
+        when(ai.loadLabel(any())).thenReturn("app name");
+        when(mPm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ZenModeConfig config = new ZenModeConfig();
+        if (!modesUi()) {
+            config.manualRule = new ZenModeConfig.ZenRule();
+        }
+        config.manualRule.conditionId = Uri.EMPTY;
+        config.manualRule.pkg = "android";
+        config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        config.manualRule.enabler = "app";
+        config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_TRUE, SOURCE_UNKNOWN);
+        assertThat(ZenModeConfig.getDescription(mContext, true, config, false))
+                .isEqualTo("app name");
+    }
+
+    @Test
+    public void testGetDescription_on_automatic() {
+        ZenModeConfig config = new ZenModeConfig();
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.configurationActivity = new ComponentName("a", "a");
+        rule.component = new ComponentName("b", "b");
+        rule.conditionId = new Uri.Builder().scheme("hello").build();
+        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+        rule.enabled = true;
+        rule.creationTime = 123;
+        rule.id = "id";
+        rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.modified = true;
+        rule.name = "name";
+        rule.snoozing = false;
+        rule.pkg = "b";
+        config.automaticRules.put("key", rule);
+
+        assertThat(ZenModeConfig.getDescription(mContext, true, config, false))
+                .isEqualTo("name");
+    }
+
     private ZenModeConfig getMutedRingerConfig() {
         ZenModeConfig config = new ZenModeConfig();
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 88a9483..60d8964 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -605,7 +605,7 @@
     public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
-        setHasFixedKeyboardAmplitudeIntensity(true);
+        setKeyboardVibrationSettingsSupported(true);
 
         // Keyboard touch ignored.
         assertVibrationIgnoredForAttributes(
@@ -630,7 +630,7 @@
     public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
-        setHasFixedKeyboardAmplitudeIntensity(true);
+        setKeyboardVibrationSettingsSupported(true);
 
         // General touch ignored.
         assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -645,10 +645,10 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
-    public void shouldIgnoreVibration_noFixedKeyboardAmplitude_ignoresKeyboardTouchVibration() {
+    public void shouldIgnoreVibration_notSupportKeyboardVibration_ignoresKeyboardTouchVibration() {
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
-        setHasFixedKeyboardAmplitudeIntensity(false);
+        setKeyboardVibrationSettingsSupported(false);
 
         // General touch ignored.
         assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -974,8 +974,8 @@
         when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
     }
 
-    private void setHasFixedKeyboardAmplitudeIntensity(boolean hasFixedAmplitude) {
-        when(mVibrationConfigMock.hasFixedKeyboardAmplitude()).thenReturn(hasFixedAmplitude);
+    private void setKeyboardVibrationSettingsSupported(boolean supported) {
+        when(mVibrationConfigMock.isKeyboardVibrationSettingsSupported()).thenReturn(supported);
     }
 
     private void deleteUserSetting(String settingName) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
index 5125594..36861fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
@@ -16,6 +16,15 @@
 
 package com.android.server.wm;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_MIGRATION;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
@@ -24,8 +33,11 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
@@ -37,6 +49,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -79,39 +93,67 @@
     @Test
     public void testInitialization() {
         initialize(true /* supportFold */, true /* supportHalfFolded */);
-        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
     }
 
     @Test
     public void testInitializationWithNoFoldSupport() {
         initialize(false /* supportFold */, false /* supportHalfFolded */);
-        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates.get(0).getIdentifier());
         // Note that the folded state is ignored.
         assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState);
     }
 
     @Test
-    public void testWithFoldSupported() {
+    @RequiresFlagsDisabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+    public void testWithFoldSupported_withOverlayConfigValues() {
         initialize(true /* supportFold */, false /* supportHalfFolded */);
-        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
-        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
-        mTarget.onDeviceStateReceivedByDisplayManager(mHalfFoldedStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mHalfFoldedStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState); // Ignored
     }
 
     @Test
-    public void testWithHalfFoldSupported() {
-        initialize(true /* supportFold */, true /* supportHalfFolded */);
-        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates[0]);
+    @RequiresFlagsEnabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+    public void testWithFoldSupported_withDeviceStateManagerPropertyAPI() {
+        initialize(true /* supportFold */, false /* supportHalfFolded */);
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
-        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
-        mTarget.onDeviceStateReceivedByDisplayManager(mHalfFoldedStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mHalfFoldedStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState); // Ignored
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+    public void testWithHalfFoldSupported_withOverlayConfigValue() {
+        initialize(true /* supportFold */, true /* supportHalfFolded */);
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+        mTarget.onDeviceStateReceivedByDisplayManager(mHalfFoldedStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.HALF_FOLDED, mCurrentState);
-        mTarget.onDeviceStateReceivedByDisplayManager(mConcurrentDisplayState);
+        mTarget.onDeviceStateReceivedByDisplayManager(mConcurrentDisplayState.getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.CONCURRENT, mCurrentState);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+    public void testWithHalfFoldSupported_withDeviceStateManagerPropertyApi() {
+        initialize(true /* supportFold */, true /* supportHalfFolded */);
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+        mTarget.onDeviceStateReceivedByDisplayManager(mHalfFoldedStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.HALF_FOLDED, mCurrentState);
+        mTarget.onDeviceStateReceivedByDisplayManager(mConcurrentDisplayState.getIdentifier());
         assertEquals(DeviceStateController.DeviceState.CONCURRENT, mCurrentState);
     }
 
@@ -121,16 +163,18 @@
         assertEquals(1, mTarget.mDeviceStateCallbacks.size());
         assertTrue(mTarget.mDeviceStateCallbacks.containsKey(mDelegate));
 
-        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
-        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates[0]);
+        mTarget.onDeviceStateReceivedByDisplayManager(mFoldedStates.get(0).getIdentifier());
         assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
 
         // The callback should not receive state change when it is unregistered.
         mTarget.unregisterDeviceStateCallback(mDelegate);
         assertTrue(mTarget.mDeviceStateCallbacks.isEmpty());
-        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates[0]);
-        assertEquals(DeviceStateController.DeviceState.FOLDED /* unchanged */, mCurrentState);
+
+        mTarget.onDeviceStateReceivedByDisplayManager(mOpenDeviceStates.get(0).getIdentifier());
+        assertEquals(DeviceStateController.DeviceState.FOLDED /* unchanged */,
+                mCurrentState);
     }
 
     @Test
@@ -151,16 +195,50 @@
         assertEquals(mExecutor, entries.get(0).second);
     }
 
-    private final int[] mFoldedStates = {0};
-    private final int[] mOpenDeviceStates = {1};
-    private final int[] mHalfFoldedStates = {2};
-    private final int[] mRearDisplayStates = {3};
-    private final int mConcurrentDisplayState = 4;
+    private final List<DeviceState> mFoldedStates = new ArrayList<>(
+            List.of(new DeviceState(new DeviceState.Configuration.Builder(0,
+                    "folded").setSystemProperties(new HashSet<>(
+                    List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)))
+                    .setPhysicalProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)))
+                    .build())));
+    private final List<DeviceState> mOpenDeviceStates = new ArrayList<>(
+            List.of(new DeviceState(new DeviceState.Configuration.Builder(1,
+                    "open").setSystemProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)))
+                    .setPhysicalProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN)))
+                    .build())));
+    private final List<DeviceState> mHalfFoldedStates = new ArrayList<>(
+            List.of(new DeviceState(new DeviceState.Configuration.Builder(2,
+                    "half_folded").setSystemProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)))
+                    .setPhysicalProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN)))
+                    .build())));
+    private final List<DeviceState> mRearDisplayStates = new ArrayList<>(
+            List.of(new DeviceState(new DeviceState.Configuration.Builder(3,
+                    "rear_display").setSystemProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                                    PROPERTY_FEATURE_REAR_DISPLAY)))
+                    .setPhysicalProperties(new HashSet<>(
+                            List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN)))
+                    .build())));
+    private final DeviceState mConcurrentDisplayState = new DeviceState(
+            new DeviceState.Configuration.Builder(4, "concurrent_display")
+                    .setSystemProperties(new HashSet<>(List.of(
+                            PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+                            PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT)))
+                    .setPhysicalProperties(new HashSet<>(List.of(
+                            PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN)))
+                    .build());
 
     private class DeviceStateControllerBuilder {
         private boolean mSupportFold = false;
         private boolean mSupportHalfFold = false;
+
         private Consumer<DeviceStateController.DeviceState> mDelegate;
+        private final List<DeviceState> mDeviceStateList = new ArrayList<>();
 
         DeviceStateControllerBuilder setSupportFold(
                 boolean supportFold, boolean supportHalfFold) {
@@ -179,13 +257,17 @@
             if (enableFold || enableHalfFold) {
                 when(mMockContext.getResources()
                         .getIntArray(R.array.config_openDeviceStates))
-                        .thenReturn(mOpenDeviceStates);
+                        .thenReturn(mapDeviceStateListToIdentifierArray(mOpenDeviceStates));
                 when(mMockContext.getResources()
                         .getIntArray(R.array.config_rearDisplayDeviceStates))
-                        .thenReturn(mRearDisplayStates);
+                        .thenReturn(mapDeviceStateListToIdentifierArray(mRearDisplayStates));
                 when(mMockContext.getResources()
                         .getInteger(R.integer.config_deviceStateConcurrentRearDisplay))
-                        .thenReturn(mConcurrentDisplayState);
+                        .thenReturn(mConcurrentDisplayState.getIdentifier());
+
+                mDeviceStateList.addAll(mOpenDeviceStates);
+                mDeviceStateList.addAll(mRearDisplayStates);
+                mDeviceStateList.add(mConcurrentDisplayState);
             } else {
                 // Match the default value in framework resources
                 when(mMockContext.getResources()
@@ -196,12 +278,14 @@
             if (enableFold) {
                 when(mMockContext.getResources()
                         .getIntArray(R.array.config_foldedDeviceStates))
-                        .thenReturn(mFoldedStates);
+                        .thenReturn(mapDeviceStateListToIdentifierArray(mFoldedStates));
+                mDeviceStateList.addAll(mFoldedStates);
             }
             if (enableHalfFold) {
                 when(mMockContext.getResources()
                         .getIntArray(R.array.config_halfFoldedDeviceStates))
-                        .thenReturn(mHalfFoldedStates);
+                        .thenReturn(mapDeviceStateListToIdentifierArray(mHalfFoldedStates));
+                mDeviceStateList.addAll(mHalfFoldedStates);
             }
         }
 
@@ -210,11 +294,20 @@
             mMockDeviceStateManager = mock(DeviceStateManager.class);
             when(mMockContext.getSystemService(DeviceStateManager.class))
                     .thenReturn(mMockDeviceStateManager);
+            when(mMockDeviceStateManager.getSupportedDeviceStates()).thenReturn(mDeviceStateList);
             Resources mockRes = mock(Resources.class);
             when(mMockContext.getResources()).thenReturn((mockRes));
             mockFold(mSupportFold, mSupportHalfFold);
             mTarget = new DeviceStateController(mMockContext, new WindowManagerGlobalLock());
             mTarget.registerDeviceStateCallback(mDelegate, mExecutor);
         }
+
+        private int[] mapDeviceStateListToIdentifierArray(List<DeviceState> deviceStates) {
+            int[] identifiers = new int[deviceStates.size()];
+            for (int i = 0; i < deviceStates.size(); i++) {
+                identifiers[i] = deviceStates.get(i).getIdentifier();
+            }
+            return identifiers;
+        }
     }
 }
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 0ddc38a..7ed26fb 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -716,14 +717,15 @@
     }
 
     private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) {
-        if (uid == Process.SYSTEM_UID || uid == Process.PHONE_UID) {
+        if (isSystemOrPhone(uid)) {
             // Skip the check if it's one of these special uids
             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
+                    Context.TELEPHONY_SERVICE);
             return telephonyManager.createForSubscriptionId(subId).getCarrierPrivilegeStatus(uid);
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -926,4 +928,30 @@
                 || checkCallingOrSelfReadPhoneNumber(context, subId, callingPackage,
                 callingFeatureId, message));
     }
+
+    /**
+     * @return true if the specified {@code uid} is for a system or phone process, no matter if runs
+     * as system user or not.
+     */
+    public static boolean isSystemOrPhone(int uid) {
+        if (Flags.supportPhoneUidCheckForMultiuser()) {
+            return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || UserHandle.isSameApp(uid,
+                    Process.PHONE_UID);
+        } else {
+            return uid == Process.SYSTEM_UID || uid == Process.PHONE_UID;
+        }
+    }
+
+    /**
+     * @return true if the specified {@code uid} is for a ROOT or SHELL process, no matter if runs
+     * as system user or not.
+     */
+    public static boolean isRootOrShell(int uid) {
+        if (Flags.supportPhoneUidCheckForMultiuser()) {
+            return UserHandle.isSameApp(uid, Process.ROOT_UID) || UserHandle.isSameApp(uid,
+                    Process.SHELL_UID);
+        } else {
+            return uid == Process.ROOT_UID || uid == Process.SHELL_UID;
+        }
+    }
 }
diff --git a/tests/Input/assets/testPointerStrokeStyle.png b/tests/Input/assets/testPointerStrokeStyle.png
new file mode 100644
index 0000000..4ddde70
--- /dev/null
+++ b/tests/Input/assets/testPointerStrokeStyle.png
Binary files differ
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
index d196b85..e0f8c6d 100644
--- a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -88,6 +88,35 @@
         theme.applyStyle(
             PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_GREEN),
             /* force= */ true)
+        theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(
+            PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE), /* force= */ true)
+
+        val pointerIcon =
+            PointerIcon.getLoadedSystemIcon(
+                ContextThemeWrapper(context, theme),
+                PointerIcon.TYPE_ARROW,
+                /* useLargeIcons= */ false,
+                /* pointerScale= */ 1f)
+
+        pointerIcon.getBitmap().assertAgainstGolden(
+            screenshotRule,
+            testName.methodName,
+            exactScreenshotMatcher
+        )
+    }
+
+    @Test
+    fun testPointerStrokeStyle() {
+        assumeTrue(enableVectorCursors())
+        assumeTrue(enableVectorCursorA11ySettings())
+
+        val theme: Resources.Theme = context.getResources().newTheme()
+        theme.setTo(context.getTheme())
+        theme.applyStyle(
+            PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK),
+            /* force= */ true)
+        theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(
+            PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_BLACK), /* force= */ true)
 
         val pointerIcon =
             PointerIcon.getLoadedSystemIcon(
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 6a17ef8..df1d51e 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -763,10 +763,35 @@
     pool->setTo(chunk, android::util::DeviceToHost32(
                            (reinterpret_cast<const ResChunk_header*>(chunk))->size));
 
-    printer_->Print("\n");
+    printer_->Print(StringPrintf(" strings: %zd styles %zd flags: %s|%s\n", pool->size(),
+                                 pool->styleCount(), pool->isUTF8() ? "UTF-8" : "UTF-16",
+                                 pool->isSorted() ? "SORTED" : "NON-SORTED"));
 
     for (size_t i = 0; i < pool->size(); i++) {
       printer_->Print(StringPrintf("#%zd : %s\n", i, android::util::GetString(*pool, i).c_str()));
+      if (i < pool->styleCount()) {
+        printer_->Print(" [Style] ");
+        auto maybe_style = pool->styleAt(i);
+        if (!maybe_style) {
+          printer_->Print("??? missing\n");
+        } else {
+          std::vector<const ResStringPool_span*> spans;
+          for (auto style = maybe_style.value().unsafe_ptr();
+               style->name.index != android::ResStringPool_span::END; ++style) {
+            spans.push_back(style);
+          }
+          printer_->Print(StringPrintf("(%zd)", spans.size()));
+          if (!spans.empty()) {
+            printer_->Print(" :");
+            for (const auto& span : spans) {
+              printer_->Print(StringPrintf(
+                  " %s:%u,%u", android::util::GetString(*pool, span->name.index).c_str(),
+                  span->firstChar, span->lastChar));
+            }
+            printer_->Print("\n");
+          }
+        }
+      }
     }
   }
 
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index d788e3f..b30348d 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -184,6 +184,35 @@
   EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello"));
 }
 
+TEST(ResourcesValuesTest, StringEquals) {
+  android::StringPool pool;
+
+  String str(pool.MakeRef("hello", android::StringPool::Context(test::ParseConfigOrDie("en"))));
+  String str2(pool.MakeRef("hello"));
+  EXPECT_TRUE(str.Equals(&str2));
+  EXPECT_TRUE(str2.Equals(&str));
+
+  String str3(pool.MakeRef("how are you"));
+  EXPECT_FALSE(str.Equals(&str3));
+}
+
+TEST(ResourcesValuesTest, StyledStringEquals) {
+  android::StringPool pool;
+
+  StyledString ss(pool.MakeRef(android::StyleString{"hello", {{"b", 0, 1}, {"u", 2, 4}}}));
+  StyledString ss2(pool.MakeRef(android::StyleString{"hello", {{"b", 0, 1}, {"u", 2, 4}}}));
+  StyledString ss3(pool.MakeRef(android::StyleString{"hi", {{"b", 0, 1}, {"u", 2, 4}}}));
+  StyledString ss4(pool.MakeRef(android::StyleString{"hello", {{"b", 0, 1}}}));
+  StyledString ss5(pool.MakeRef(android::StyleString{"hello", {{"b", 0, 1}, {"u", 3, 4}}}));
+  StyledString ss6(pool.MakeRef(android::StyleString{"hello", {{"b", 0, 1}, {"s", 2, 4}}}));
+  EXPECT_TRUE(ss.Equals(&ss2));
+  EXPECT_TRUE(ss2.Equals(&ss));
+  EXPECT_FALSE(ss.Equals(&ss3));
+  EXPECT_FALSE(ss.Equals(&ss4));
+  EXPECT_FALSE(ss.Equals(&ss5));
+  EXPECT_FALSE(ss.Equals(&ss6));
+}
+
 TEST(ResourceValuesTest, StyleMerges) {
   android::StringPool pool_a;
   android::StringPool pool_b;
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 5bfc732..6da3176 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -106,7 +106,7 @@
   if (!value_a->Equals(value_b)) {
     std::stringstream str_stream;
     str_stream << "value " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name
-               << " config=" << config_value_a->config << " does not match:\n";
+               << " config='" << config_value_a->config << "' does not match:\n";
     value_a->Print(&str_stream);
     str_stream << "\n vs \n";
     value_b->Print(&str_stream);
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index ba4a734..14298d16 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -127,4 +127,4 @@
 
 } // namespace aapt
 
-#endif  // AAPT_TEST_FIXTURE_H
\ No newline at end of file
+#endif  // AAPT_TEST_FIXTURE_H