Merge "Kill apps when GIDs changed instead of only when added" into main
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/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/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/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/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/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/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/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/symbols.xml b/core/res/res/values/symbols.xml
index 26f153a..9661b46 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2126,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/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index dfc9c82..8f7fdd6 100644
--- 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
@@ -37,15 +37,12 @@
   // All desktop mode related flags will be added here
   DESKTOP_WINDOWING_MODE(DesktopModeStatus::isDesktopModeFlagEnabled, true);
 
-  private val TAG = "DesktopModeFlags"
-
-  // 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.
+  // 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.
+   * 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.
    */
@@ -63,34 +60,44 @@
       }
 
   private fun getToggleOverride(context: Context): ToggleOverride {
-    val override = cachedToggleOverride ?: run {
-      // Cache toggle override the first time we encounter context. Override does not change
-      // with context, as context is just used to fetch a system property.
-
-      // TODO(b/348193756): Cache a persistent value for Settings.Global until reboot. Current
-      //  cache will change with process restart.
-      val toggleOverride =
-          Settings.Global.getInt(
-              context.contentResolver,
-              Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
-              ToggleOverride.OVERRIDE_UNSET.setting)
-
-      val newOverride =
-          settingToToggleOverrideMap[toggleOverride]
-              ?: run {
-                Log.w(TAG, "Unknown toggleOverride $toggleOverride")
-                ToggleOverride.OVERRIDE_UNSET
-              }
-      cachedToggleOverride = newOverride
-      Log.d(TAG, "Toggle override initialized to: $newOverride")
-      newOverride
-    }
+    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
   }
 
-  // TODO(b/348193756): Share ToggleOverride enum with Settings
-  // 'DesktopModePreferenceController'
+  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.
    *
@@ -107,4 +114,33 @@
   }
 
   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/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/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/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 b8abf8f..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
@@ -242,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;
         }
@@ -1034,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());
@@ -1108,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);
@@ -1128,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) {
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
index 115b218..17983b2 100644
--- 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
@@ -82,7 +82,7 @@
 
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_unsetOverride_featureFlagOn_returnsTrue() {
+  fun isEnabled_overrideUnset_featureFlagOn_returnsTrue() {
     setOverride(OVERRIDE_UNSET.setting)
 
     // For overridableFlag, for unset overrides, follow flag
@@ -92,7 +92,7 @@
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
   @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_unsetOverride_featureFlagOff_returnsFalse() {
+  fun isEnabled_overrideUnset_featureFlagOff_returnsFalse() {
     setOverride(OVERRIDE_UNSET.setting)
 
     // For overridableFlag, for unset overrides, follow flag
@@ -101,7 +101,7 @@
 
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_noOverride_featureFlagOn_returnsTrue() {
+  fun isEnabled_noOverride_featureFlagOn_returnsTrue() {
     setOverride(null)
 
     // For overridableFlag, in absence of overrides, follow flag
@@ -111,7 +111,7 @@
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
   @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_noOverride_featureFlagOff_returnsFalse() {
+  fun isEnabled_noOverride_featureFlagOff_returnsFalse() {
     setOverride(null)
 
     // For overridableFlag, in absence of overrides, follow flag
@@ -120,7 +120,7 @@
 
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_unrecognizableOverride_featureFlagOn_returnsTrue() {
+  fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() {
     setOverride(-2)
 
     // For overridableFlag, for recognizable overrides, follow flag
@@ -130,7 +130,7 @@
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
   @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_unrecognizableOverride_featureFlagOff_returnsFalse() {
+  fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() {
     setOverride(-2)
 
     // For overridableFlag, for recognizable overrides, follow flag
@@ -139,7 +139,7 @@
 
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_overrideOff_featureFlagOn_returnsFalse() {
+  fun isEnabled_overrideOff_featureFlagOn_returnsFalse() {
     setOverride(OVERRIDE_OFF.setting)
 
     // For overridableFlag, follow override if they exist
@@ -149,7 +149,7 @@
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
   @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_overrideOn_featureFlagOff_returnsTrue() {
+  fun isEnabled_overrideOn_featureFlagOff_returnsTrue() {
     setOverride(OVERRIDE_ON.setting)
 
     // For overridableFlag, follow override if they exist
@@ -158,7 +158,7 @@
 
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
+  fun isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
     setOverride(OVERRIDE_OFF.setting)
 
     // For overridableFlag, follow override if they exist
@@ -173,7 +173,7 @@
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
   @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
+  fun isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
     setOverride(OVERRIDE_ON.setting)
 
     // For overridableFlag, follow override if they exist
@@ -187,7 +187,7 @@
 
   @Test
   @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-  fun isEnabled_flagOverridable_noOverride_featureFlagOnThenOff_returnsTrueAndFalse() {
+  fun isEnabled_noOverride_featureFlagOnThenOff_returnsTrueAndFalse() {
     setOverride(null)
     // For overridableFlag, in absence of overrides, follow flag
     assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
@@ -206,6 +206,108 @@
     }
   }
 
+  @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
@@ -217,9 +319,16 @@
   }
 
   private fun resetCache() {
-    val cacheToggleOverride =
+    val cachedToggleOverride =
         DESKTOP_WINDOWING_MODE::class.java.getDeclaredField("cachedToggleOverride")
-    cacheToggleOverride.isAccessible = true
-    cacheToggleOverride.set(DESKTOP_WINDOWING_MODE, null)
+    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/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/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/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/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/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/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/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/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 22ab82b..ab1194e 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
@@ -51,6 +51,7 @@
 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
@@ -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/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/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/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/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/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/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/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/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/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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1dfdc55..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 (mExperimentalConcurrentMultiUserModeEnabled || 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/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/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/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/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/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/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/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) {